Compare commits

..

No commits in common. "master" and "fix/migrate-to-new-progressbar" have entirely different histories.

52 changed files with 1749 additions and 2698 deletions

2
.gitignore vendored

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

@ -44,7 +44,3 @@ issues:
- path: _test\.go - path: _test\.go
linters: linters:
- errcheck - errcheck
# TODO: remove
- linters:
- staticcheck
text: "SA1019: interp.ExecHandler"

@ -1,42 +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/>.
repos:
- repo: local
hooks:
- id: test-coverage
name: Run test coverage
entry: make test-coverage
language: system
pass_filenames: false
- id: fmt
name: Format code
entry: make fmt
language: system
pass_filenames: false
- id: update-license
name: Update license
entry: make update-license
language: system
pass_filenames: false
- id: i18n
name: Update i18n
entry: make i18n
language: system
pass_filenames: false

@ -12,7 +12,7 @@ INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completio
INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME) INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME)
ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524 ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524
GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0 XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0
.PHONY: build install clean clear uninstall check-no-root .PHONY: build install clean clear uninstall check-no-root
@ -66,7 +66,3 @@ 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
test-coverage:
go test ./... -v -coverpkg=./... -coverprofile=coverage.out
bash scripts/coverage-badge.sh

@ -3,8 +3,6 @@
</p> </p>
<b></b> <b></b>
[![Go Report Card](https://goreportcard.com/badge/gitea.plemya-x.ru/Plemya-x/ALR)](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR) ![Test coverage](./coverage-badge.svg)
# ALR (Any Linux Repository) # ALR (Any Linux Repository)
ALR - это независимая от дистрибутива система сборки для Linux, аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. alr готов к общему использованию, но все еще может время от времени ломаться или заменяться. ALR - это независимая от дистрибутива система сборки для Linux, аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. alr готов к общему использованию, но все еще может время от времени ломаться или заменяться.
@ -50,12 +48,7 @@ ALR был создан потому, что упаковка программн
## Репозитории ## Репозитории
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD. Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD. Репозиторий [по-умолчанию](https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git).
Например, репозиторий [Plemya-x/xpamych-alr-repo](https://gitea.plemya-x.ru/Plemya-x/xpamych-alr-repo.git) можно подключить так:
```
alr addrepo --name xpamych-alr-repo --url https://gitea.plemya-x.ru/Plemya-x/xpamych-alr-repo.git
```
--- ---
## Соцсети ## Соцсети

@ -23,17 +23,14 @@ 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"
) )
@ -49,11 +46,6 @@ 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: "script-package",
Aliases: []string{"sp"},
Usage: gotext.Get("Specify package in script (for multi package script only)"),
},
&cli.StringFlag{ &cli.StringFlag{
Name: "package", Name: "package",
Aliases: []string{"p"}, Aliases: []string{"p"},
@ -67,106 +59,52 @@ 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
// Проверяем, установлен ли флаг script (-s)
repoDir := cfg.GetPaths(ctx).RepoDir
switch {
case c.IsSet("script"):
script = c.String("script")
packages = append(packages, c.String("script-package"))
case c.IsSet("package"):
// TODO: handle multiple packages
packageInput := c.String("package") packageInput := c.String("package")
arr := strings.Split(packageInput, "/") if packageInput != "" {
var packageSearch string // If the package input contains a '/', use it as the full path.
if len(arr) == 2 { if filepath.Dir(packageInput) == "." {
packageSearch = arr[1] // No directory specified, use 'default' as a prefix.
script = filepath.Join(config.GetPaths(ctx).RepoDir, "default", packageInput, "alr.sh")
} else { } else {
packageSearch = arr[0] // Use the full path specified by the user.
script = filepath.Join(config.GetPaths(ctx).RepoDir, packageInput, "alr.sh")
}
} }
pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch}) if config.GetInstance(ctx).AutoPull(ctx) {
pkg, ok := pkgs[packageSearch] err := repos.Pull(ctx, config.Config(ctx).Repos)
if len(pkg) < 1 || !ok {
slog.Error(gotext.Get("Package not found"))
os.Exit(1)
}
if pkg[0].BasePkgName != "" {
script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].BasePkgName, "alr.sh")
packages = append(packages, pkg[0].Name)
} else {
script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].Name, "alr.sh")
}
default:
script = filepath.Join(repoDir, "alr.sh")
}
// Проверка автоматического пулла репозиториев
if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { 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)
} }
} }
// Обнаружение менеджера пакетов
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1) os.Exit(1)
} }
info, err := distro.ParseOSRelease(ctx) pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
}
builder := build.NewBuilder(
ctx,
types.BuildOpts{
Packages: packages,
Script: script, Script: script,
Manager: mgr, Manager: mgr,
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
}, })
rs,
info,
cfg,
)
// Сборка пакета
pkgPaths, _, err := builder.BuildPackage(ctx)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error building package"), "err", err) slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1) os.Exit(1)
} }
// Получение текущей рабочей директории
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err) slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1) os.Exit(1)
} }
// Перемещение собранных пакетов в рабочую директорию
for _, pkgPath := range pkgPaths { for _, pkgPath := range pkgPaths {
name := filepath.Base(pkgPath) name := filepath.Base(pkgPath)
err = osutils.Move(pkgPath, filepath.Join(wd, name)) err = osutils.Move(pkgPath, filepath.Join(wd, name))

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

Before

(image error) Size: 926 B

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"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
@ -37,8 +37,9 @@ 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()
paths := cfg.GetPaths(ctx) db.Close()
paths := config.GetPaths(ctx)
slog.Info(gotext.Get("Removing cache directory")) slog.Info(gotext.Get("Removing cache directory"))
@ -56,15 +57,7 @@ func FixCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
cfg = config.New() err = repos.Pull(ctx, config.Config(ctx).Repos)
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)

6
go.mod

@ -5,7 +5,6 @@ go 1.22
toolchain go1.23.5 toolchain go1.23.5
require ( require (
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0 github.com/PuerkitoBio/purell v1.2.0
github.com/alecthomas/chroma/v2 v2.9.1 github.com/alecthomas/chroma/v2 v2.9.1
@ -15,7 +14,6 @@ require (
github.com/charmbracelet/log v0.4.0 github.com/charmbracelet/log v0.4.0
github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.12.0 github.com/go-git/go-git/v5 v5.12.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/goreleaser/nfpm/v2 v2.41.0 github.com/goreleaser/nfpm/v2 v2.41.0
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
@ -25,6 +23,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/muesli/reflow v0.3.0 github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.1.0 github.com/pelletier/go-toml/v2 v2.1.0
github.com/schollz/progressbar/v3 v3.18.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/vmihailenco/msgpack/v5 v5.3.5
@ -36,6 +35,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.25.0 modernc.org/sqlite v1.25.0
mvdan.cc/sh/v3 v3.10.0 mvdan.cc/sh/v3 v3.10.0
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283
) )
require ( require (
@ -58,6 +58,7 @@ require (
github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.3.8 // indirect github.com/cloudflare/circl v1.3.8 // indirect
github.com/connesc/cipherio v0.2.1 // indirect github.com/connesc/cipherio v0.2.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
@ -89,6 +90,7 @@ require (
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect

31
go.sum

@ -17,8 +17,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1 h1:c7F4OsyQbiVpSOrYGMrNsRL37BwoOfrgoKxAwULBKZo=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
@ -75,20 +73,36 @@ github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTli
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c=
github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM= github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -98,6 +112,8 @@ github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqw
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@ -180,8 +196,6 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S3
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -266,6 +280,8 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -274,6 +290,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@ -312,6 +330,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
@ -469,6 +489,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -609,6 +630,8 @@ modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4= mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 h1:BXCLPeA8g2M6qYngicyxyB/2Bo4J54Q9Rb+8jMmE3ik=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283/go.mod h1:itzL9Jx52VXOhRaucFHuMpa3y7iwjnuLGdNvypoh/S4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

41
info.go

@ -24,14 +24,13 @@ 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"
database "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/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"
@ -48,39 +47,11 @@ 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 := database.New(cfg) db := db.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)
@ -117,12 +88,6 @@ 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 {
@ -132,7 +97,7 @@ func InfoCmd() *cli.Command {
names, err = overrides.Resolve( names, err = overrides.Resolve(
info, info,
overrides.DefaultOpts. overrides.DefaultOpts.
WithLanguages([]string{systemLang}), WithLanguages([]string{config.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)

@ -29,10 +29,9 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "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/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"
) )
@ -64,52 +63,22 @@ func InstallCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
cfg := config.New() if config.GetInstance(ctx).AutoPull(ctx) {
db := database.New(cfg) err := repos.Pull(ctx, config.Config(ctx).Repos)
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 := rs.FindPkgs(ctx, args.Slice()) found, notFound, err := repos.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"),
@ -117,8 +86,6 @@ 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)
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)
@ -127,7 +94,7 @@ func InstallCmd() *cli.Command {
defer result.Close() defer result.Close()
for result.Next() { for result.Next() {
var pkg database.Package var pkg db.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)

@ -45,8 +45,13 @@ var defaultConfig = &types.Config{
RootCmd: "sudo", RootCmd: "sudo",
PagerStyle: "native", PagerStyle: "native",
IgnorePkgUpdates: []string{}, IgnorePkgUpdates: []string{},
AutoPull: false, AutoPull: true,
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 {
@ -152,13 +157,6 @@ 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)
@ -172,24 +170,3 @@ 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
}

@ -0,0 +1,52 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
import (
"context"
"sync"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
// Config returns a ALR configuration struct.
// The first time it's called, it'll load the config from a file.
// Subsequent calls will just return the same value.
//
// Deprecated: use struct method
func Config(ctx context.Context) *types.Config {
return GetInstance(ctx).cfg
}
// =======================
// FOR LEGACY ONLY
// =======================
var (
alrConfig *ALRConfig
alrConfigOnce sync.Once
)
// Deprecated: For legacy only
func GetInstance(ctx context.Context) *ALRConfig {
alrConfigOnce.Do(func() {
alrConfig = New()
alrConfig.Load(ctx)
})
return alrConfig
}

69
internal/config/lang.go Normal file

@ -0,0 +1,69 @@
// 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()
defer langMtx.Unlock()
if !langSet {
syslang := SystemLang()
tag, err := language.Parse(syslang)
if err != nil {
slog.Error(gotext.Get("Error parsing system language"), "err", err)
os.Exit(1)
}
base, _ := tag.Base()
lang = language.Make(base.String())
langSet = true
}
return lang
}
// SystemLang returns the system language based on
// the $LANG environment variable.
func SystemLang() string {
lang := os.Getenv("LANG")
lang, _, _ = strings.Cut(lang, ".")
if lang == "" || lang == "C" {
lang = "en"
}
return lang
}

@ -19,6 +19,10 @@
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
@ -28,3 +32,14 @@ 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.
//
// Depreacted: use struct API
func GetPaths(ctx context.Context) *Paths {
alrConfig := GetInstance(ctx)
return alrConfig.GetPaths(ctx)
}

@ -38,12 +38,11 @@ func armVariant() string {
return armEnv return armEnv
} }
switch { if cpu.ARM.HasVFPv3 {
case cpu.ARM.HasVFPv3:
return "arm7" return "arm7"
case cpu.ARM.HasVFP: } else if cpu.ARM.HasVFP {
return "arm6" return "arm6"
default: } else {
return "arm5" return "arm5"
} }
} }

@ -31,11 +31,10 @@ 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 = 3 const CurrentVersion = 2
// 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"`
@ -100,7 +99,6 @@ 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,
@ -131,10 +129,7 @@ func (d *Database) initDB(ctx context.Context) error {
ver, ok := d.GetVersion(ctx) ver, ok := d.GetVersion(ctx)
if ok && ver != CurrentVersion { if ok && ver != CurrentVersion {
slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion)
err = d.reset(ctx) d.reset(ctx)
if err != nil {
return err
}
return d.initDB(ctx) return d.initDB(ctx)
} else if !ok { } else if !ok {
slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion) slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion)
@ -198,7 +193,6 @@ 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,
@ -216,7 +210,6 @@ func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
builddepends, builddepends,
optdepends optdepends
) VALUES ( ) VALUES (
:basepkg_name,
:name, :name,
:repository, :repository,
:version, :version,

106
internal/db/db_legacy.go Normal file

@ -0,0 +1,106 @@
// 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
}

@ -42,6 +42,9 @@ import (
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s" "golang.org/x/crypto/blake2s"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
) )
// Константа для имени файла манифеста кэша // Константа для имени файла манифеста кэша
@ -80,11 +83,6 @@ func (t Type) String() string {
return "<unknown>" return "<unknown>"
} }
type DlCache interface {
Get(context.Context, string) (string, bool)
New(context.Context, string) (string, error)
}
// Структура Options содержит параметры для загрузки файлов и каталогов // Структура Options содержит параметры для загрузки файлов и каталогов
type Options struct { type Options struct {
Hash []byte Hash []byte
@ -96,7 +94,6 @@ type Options struct {
PostprocDisabled bool PostprocDisabled bool
Progress io.Writer Progress io.Writer
LocalDir string LocalDir string
DlCache DlCache
} }
// Метод для создания нового хеша на основе указанного алгоритма хеширования // Метод для создания нового хеша на основе указанного алгоритма хеширования
@ -148,6 +145,9 @@ type UpdatingDownloader interface {
// Функция Download загружает файл или каталог с использованием указанных параметров // Функция Download загружает файл или каталог с использованием указанных параметров
func Download(ctx context.Context, opts Options) (err error) { func Download(ctx context.Context, opts Options) (err error) {
cfg := config.GetInstance(ctx)
dc := dlcache.New(cfg)
normalized, err := normalizeURL(opts.URL) normalized, err := normalizeURL(opts.URL)
if err != nil { if err != nil {
return err return err
@ -162,7 +162,7 @@ func Download(ctx context.Context, opts Options) (err error) {
} }
var t Type var t Type
cacheDir, ok := opts.DlCache.Get(ctx, opts.URL) cacheDir, ok := dc.Get(ctx, opts.URL)
if ok { if ok {
var updated bool var updated bool
if d, ok := d.(UpdatingDownloader); ok { if d, ok := d.(UpdatingDownloader); ok {
@ -221,7 +221,7 @@ func Download(ctx context.Context, opts Options) (err error) {
slog.Info(gotext.Get("Downloading source"), "source", opts.Name, "downloader", d.Name()) slog.Info(gotext.Get("Downloading source"), "source", opts.Name, "downloader", d.Name())
cacheDir, err = opts.DlCache.New(ctx, opts.URL) cacheDir, err = dc.New(ctx, opts.URL)
if err != nil { if err != nil {
return err return err
} }

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

@ -123,7 +123,6 @@ 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 {
@ -144,6 +143,7 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string,
return 0, "", err return 0, "", err
} }
r.Close() r.Close()
out.Close()
// Проверка контрольной суммы // Проверка контрольной суммы
if opts.Hash != nil { if opts.Hash != nil {

@ -68,7 +68,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.remaining = time.Duration(msg.remaining) * time.Second m.remaining = time.Duration(msg.remaining) * time.Second
if m.percent >= 1.0 { if m.percent >= 1.0 {
m.done = true m.done = true
return m, tea.Quit
} }
return m, nil return m, nil
case tea.WindowSizeMsg: case tea.WindowSizeMsg:

@ -32,6 +32,14 @@ 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
} }

@ -55,12 +55,12 @@ func copyDirOrFile(sourcePath, destPath string) error {
return err return err
} }
switch { if sourceInfo.IsDir() {
case sourceInfo.IsDir():
return copyDir(sourcePath, destPath, sourceInfo) return copyDir(sourcePath, destPath, sourceInfo)
case sourceInfo.Mode().IsRegular(): } else if sourceInfo.Mode().IsRegular() {
return copyFile(sourcePath, destPath, sourceInfo) return copyFile(sourcePath, destPath, sourceInfo)
default: } else {
// ignore non-regular files
return nil return nil
} }
} }

@ -41,7 +41,7 @@ func init() {
b2 := lipgloss.RoundedBorder() b2 := lipgloss.RoundedBorder()
b2.Left = "\u2524" b2.Left = "\u2524"
infoStyle = titleStyle.BorderStyle(b2) infoStyle = titleStyle.Copy().BorderStyle(b2)
} }
type Pager struct { type Pager struct {

@ -164,15 +164,26 @@ func (d *Decoder) DecodeVars(val any) error {
return nil return nil
} }
type ( type ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
ScriptFuncWithSubshell func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error)
)
// GetFunc returns a function corresponding to a bash function // GetFunc returns a function corresponding to a bash function
// with the given name // with the given name
func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) { func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
return d.GetFuncP(name, nil) fn := d.getFunc(name)
if fn == nil {
return nil, false
}
return func(ctx context.Context, opts ...interp.RunnerOption) error {
sub := d.Runner.Subshell()
for _, opt := range opts {
err := opt(sub)
if err != nil {
return err
}
}
return sub.Run(ctx, fn)
}, true
} }
type PrepareFunc func(context.Context, *interp.Runner) error type PrepareFunc func(context.Context, *interp.Runner) error
@ -200,24 +211,6 @@ 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"
"plemya-x.ru/fakeroot"
) )
// FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go // FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go

@ -22,11 +22,10 @@ package handlers
import ( import (
"context" "context"
"io" "io"
"io/fs"
"os" "os"
) )
func NopReadDir(context.Context, string) ([]fs.DirEntry, error) { func NopReadDir(context.Context, string) ([]os.FileInfo, 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.ReadDirHandlerFunc2 { func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc {
return func(ctx context.Context, s string) ([]fs.DirEntry, error) { return func(ctx context.Context, s string) ([]fs.FileInfo, 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.DefaultReadDirHandler2()(ctx, s) return interp.DefaultReadDirHandler()(ctx, s)
} }
} }

@ -245,13 +245,10 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error {
return fmt.Errorf("git-version: %w", err) return fmt.Errorf("git-version: %w", err)
} }
err = commits.ForEach(func(*object.Commit) error { commits.ForEach(func(*object.Commit) error {
revNum++ revNum++
return nil return nil
}) })
if err != nil {
return fmt.Errorf("git-version: %w", err)
}
HEAD, err := r.Head() HEAD, err := r.Head()
if err != nil { if err != nil {

@ -9,56 +9,40 @@ 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:44 #: build.go:41
msgid "Build a local package" msgid "Build a local package"
msgstr "" msgstr ""
#: build.go:50 #: build.go:47
msgid "Path to the build script" msgid "Path to the build script"
msgstr "" msgstr ""
#: build.go:55 #: build.go:52
msgid "Specify package 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:65 #: build.go:57
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:75 #: build.go:80
msgid "Error initialization database"
msgstr ""
#: build.go:105
msgid "Package not found"
msgstr ""
#: build.go:123
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "" msgstr ""
#: build.go:131 #: build.go:87
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:137 #: build.go:98
msgid "Error parsing os release"
msgstr ""
#: build.go:158
msgid "Error building package" msgid "Error building package"
msgstr "" msgstr ""
#: build.go:165 #: build.go:104
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "" msgstr ""
#: build.go:174 #: build.go:112
msgid "Error moving the package" msgid "Error moving the package"
msgstr "" msgstr ""
@ -66,27 +50,27 @@ msgstr ""
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "" msgstr ""
#: fix.go:43 #: fix.go:44
msgid "Removing cache directory" msgid "Removing cache directory"
msgstr "" msgstr ""
#: fix.go:47 #: fix.go:48
msgid "Unable to remove cache directory" msgid "Unable to remove cache directory"
msgstr "" msgstr ""
#: fix.go:51 #: fix.go:52
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "" msgstr ""
#: fix.go:55 #: fix.go:56
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "" msgstr ""
#: fix.go:69 #: fix.go:62
msgid "Error pulling repos" msgid "Error pulling repos"
msgstr "" msgstr ""
#: fix.go:73 #: fix.go:66
msgid "Done" msgid "Done"
msgstr "" msgstr ""
@ -114,59 +98,63 @@ msgstr ""
msgid "No such helper command" msgid "No such helper command"
msgstr "" msgstr ""
#: info.go:43 #: info.go:42
msgid "Print information about a package" msgid "Print information about a package"
msgstr "" msgstr ""
#: info.go:48 #: info.go:47
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "" msgstr ""
#: info.go:63 #: info.go:57
msgid "Error getting packages" msgid "Error initialization database"
msgstr "" msgstr ""
#: info.go:72 #: info.go:64
msgid "Error iterating over packages"
msgstr ""
#: info.go:93
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: info.go:107 #: info.go:78
msgid "Error finding packages" msgid "Error finding packages"
msgstr "" msgstr ""
#: info.go:129 #: info.go:94
msgid "Error parsing os-release file" msgid "Error parsing os-release file"
msgstr "" msgstr ""
#: info.go:138 #: info.go:103
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "" msgstr ""
#: info.go:147 info.go:153 #: info.go:112 info.go:118
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "" msgstr ""
#: install.go:43 #: install.go:42
msgid "Install a new package" msgid "Install a new package"
msgstr "" msgstr ""
#: install.go:57 #: install.go:56
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:146 #: install.go:91
msgid "Error getting packages"
msgstr ""
#: install.go:100
msgid "Error iterating over packages"
msgstr ""
#: install.go:113
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "" msgstr ""
#: install.go:151 #: install.go:118
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:163 #: install.go:130
msgid "Error removing packages" msgid "Error removing packages"
msgstr "" msgstr ""
@ -194,51 +182,59 @@ msgstr ""
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "" msgstr ""
#: internal/config/config.go:59 #: internal/config/config.go:64
msgid "Error opening config file, using defaults" msgid "Error opening config file, using defaults"
msgstr "" msgstr ""
#: internal/config/config.go:72 #: internal/config/config.go:77
msgid "Error decoding config file, using defaults" msgid "Error decoding config file, using defaults"
msgstr "" msgstr ""
#: internal/config/config.go:84 #: internal/config/config.go:89
msgid "Unable to detect user config directory" msgid "Unable to detect user config directory"
msgstr "" msgstr ""
#: internal/config/config.go:92 #: internal/config/config.go:97
msgid "Unable to create ALR config directory" msgid "Unable to create ALR config directory"
msgstr "" msgstr ""
#: internal/config/config.go:101 #: internal/config/config.go:106
msgid "Unable to create ALR config file" msgid "Unable to create ALR config file"
msgstr "" msgstr ""
#: internal/config/config.go:107 #: internal/config/config.go:112
msgid "Error encoding default configuration" msgid "Error encoding default configuration"
msgstr "" msgstr ""
#: internal/config/config.go:116 #: internal/config/config.go:121
msgid "Unable to detect cache directory" msgid "Unable to detect cache directory"
msgstr "" msgstr ""
#: internal/config/config.go:126 #: internal/config/config.go:131
msgid "Unable to create repo cache directory" msgid "Unable to create repo cache directory"
msgstr "" msgstr ""
#: internal/config/config.go:132 #: internal/config/config.go:137
msgid "Unable to create package cache directory" msgid "Unable to create package cache directory"
msgstr "" msgstr ""
#: internal/db/db.go:133 #: internal/config/lang.go:50
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:140 #: internal/db/db.go:135
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 ""
@ -255,11 +251,11 @@ msgstr ""
msgid "Downloading source" msgid "Downloading source"
msgstr "" msgstr ""
#: internal/dl/progress_tui.go:100 #: internal/dl/progress_tui.go:99
msgid "%s: done!\n" msgid "%s: done!\n"
msgstr "" msgstr ""
#: internal/dl/progress_tui.go:104 #: internal/dl/progress_tui.go:103
msgid "%s %s downloading at %s/s\n" msgid "%s %s downloading at %s/s\n"
msgstr "" msgstr ""
@ -275,92 +271,104 @@ msgstr ""
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "" msgstr ""
#: main.go:44 #: main.go:45
msgid "Print the current ALR version and exit" msgid "Print the current ALR version and exit"
msgstr "" msgstr ""
#: main.go:60 #: main.go:61
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "" msgstr ""
#: main.go:66 #: main.go:67
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "" msgstr ""
#: main.go:91 #: main.go:90
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:123 #: main.go:124
msgid "Error while running app" msgid "Error while running app"
msgstr "" msgstr ""
#: pkg/build/build.go:153 #: pkg/build/build.go:107
msgid "Failed to prompt user to view build script" msgid "Failed to prompt user to view build script"
msgstr "" msgstr ""
#: pkg/build/build.go:157 #: pkg/build/build.go:111
msgid "Building package" msgid "Building package"
msgstr "" msgstr ""
#: pkg/build/build.go:228 #: pkg/build/build.go:155
msgid "Downloading sources" msgid "Downloading sources"
msgstr "" msgstr ""
#: pkg/build/build.go:246 #: pkg/build/build.go:167
msgid "Building package metadata" msgid "Building package metadata"
msgstr "" msgstr ""
#: pkg/build/build.go:268 #: pkg/build/build.go:189
msgid "Compressing package" msgid "Compressing package"
msgstr "" msgstr ""
#: pkg/build/build.go:421 #: pkg/build/build.go:315
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:435 #: pkg/build/build.go:326
msgid "This package is already installed" msgid "This package is already installed"
msgstr "" msgstr ""
#: pkg/build/build.go:459 #: pkg/build/build.go:354
msgid "Installing build dependencies" msgid "Installing build dependencies"
msgstr "" msgstr ""
#: pkg/build/build.go:500 #: pkg/build/build.go:396
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "" msgstr ""
#: pkg/build/build.go:535 #: pkg/build/build.go:442
msgid "The checksums array must be the same length as sources" msgid "Executing version()"
msgstr "" msgstr ""
#: pkg/build/build.go:586 #: pkg/build/build.go:462
msgid "Would you like to remove the build dependencies?" msgid "Updating version"
msgstr "" msgstr ""
#: pkg/build/build.go:649 #: pkg/build/build.go:467
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "" msgstr ""
#: pkg/build/build.go:659 #: pkg/build/build.go:477
msgid "Executing build()" msgid "Executing build()"
msgstr "" msgstr ""
#: pkg/build/build.go:689 pkg/build/build.go:709 #: pkg/build/build.go:489
msgid "Executing %s()" msgid "Executing package()"
msgstr "" msgstr ""
#: pkg/build/build.go:768 #: pkg/build/build.go:527
msgid "Error installing native packages" msgid "Executing files()"
msgstr "" msgstr ""
#: pkg/build/build.go:792 #: pkg/build/build.go:605
msgid "Error installing package" msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/build.go:616
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/build.go:723
msgid "Would you like to remove the build dependencies?"
msgstr ""
#: pkg/build/build.go:829
msgid "The checksums array must be the same length as sources"
msgstr "" msgstr ""
#: pkg/build/findDeps.go:35 #: pkg/build/findDeps.go:35
@ -375,27 +383,27 @@ msgstr ""
msgid "Required dependency found" msgid "Required dependency found"
msgstr "" msgstr ""
#: pkg/build/utils.go:133 #: pkg/build/install.go:42
msgid "AutoProv is not implemented for this package format, so it's skipped" msgid "Error installing native packages"
msgstr "" msgstr ""
#: pkg/build/utils.go:144 #: pkg/build/install.go:79
msgid "AutoReq is not implemented for this package format, so it's skipped" msgid "Error installing package"
msgstr "" msgstr ""
#: pkg/repos/pull.go:79 #: pkg/repos/pull.go:75
msgid "Pulling repository" msgid "Pulling repository"
msgstr "" msgstr ""
#: pkg/repos/pull.go:103 #: pkg/repos/pull.go:99
msgid "Repository up to date" msgid "Repository up to date"
msgstr "" msgstr ""
#: pkg/repos/pull.go:160 #: pkg/repos/pull.go:156
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:176 #: pkg/repos/pull.go:172
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."
@ -413,78 +421,46 @@ msgstr ""
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "" msgstr ""
#: repo.go:82 repo.go:147 #: repo.go:79 repo.go:136
msgid "Error opening config file" msgid "Error opening config file"
msgstr "" msgstr ""
#: repo.go:88 repo.go:153 #: repo.go:85 repo.go:142
msgid "Error encoding config" msgid "Error encoding config"
msgstr "" msgstr ""
#: repo.go:113 #: repo.go:103
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "" msgstr ""
#: repo.go:120 #: repo.go:110
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "" msgstr ""
#: repo.go:139 #: repo.go:128
msgid "Repo does not exist" msgid "Repo does not exist"
msgstr "" msgstr ""
#: repo.go:159 #: repo.go:148
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "" msgstr ""
#: repo.go:170 #: repo.go:154
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "" msgstr ""
#: repo.go:182 #: repo.go:166
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:90 #: upgrade.go:83
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "" msgstr ""
#: upgrade.go:112 #: upgrade.go:94
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "" msgstr ""

@ -16,57 +16,40 @@ 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:44 #: build.go:41
msgid "Build a local package" msgid "Build a local package"
msgstr "Сборка локального пакета" msgstr "Сборка локального пакета"
#: build.go:50 #: build.go:47
msgid "Path to the build script" msgid "Path to the build script"
msgstr "Путь к скрипту сборки" msgstr "Путь к скрипту сборки"
#: build.go:55 #: build.go:52
msgid "Specify package 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:65 #: build.go:57
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:75 #: build.go:80
msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных"
#: build.go:105
msgid "Package not found"
msgstr ""
#: build.go:123
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев" msgstr "Ошибка при извлечении репозиториев"
#: build.go:131 #: build.go:87
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:137 #: build.go:98
#, fuzzy
msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: build.go:158
msgid "Error building package" msgid "Error building package"
msgstr "Ошибка при сборке пакета" msgstr "Ошибка при сборке пакета"
#: build.go:165 #: build.go:104
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога" msgstr "Ошибка при получении рабочего каталога"
#: build.go:174 #: build.go:112
msgid "Error moving the package" msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета" msgstr "Ошибка при перемещении пакета"
@ -74,27 +57,27 @@ msgstr "Ошибка при перемещении пакета"
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR" msgstr "Попытка устранить проблемы с ALR"
#: fix.go:43 #: fix.go:44
msgid "Removing cache directory" msgid "Removing cache directory"
msgstr "Удаление каталога кэша" msgstr "Удаление каталога кэша"
#: fix.go:47 #: fix.go:48
msgid "Unable to remove cache directory" msgid "Unable to remove cache directory"
msgstr "Не удалось удалить каталог кэша" msgstr "Не удалось удалить каталог кэша"
#: fix.go:51 #: fix.go:52
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "Восстановление кэша" msgstr "Восстановление кэша"
#: fix.go:55 #: fix.go:56
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша" msgstr "Не удалось создать новый каталог кэша"
#: fix.go:69 #: fix.go:62
msgid "Error pulling repos" msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев" msgstr "Ошибка при извлечении репозиториев"
#: fix.go:73 #: fix.go:66
msgid "Done" msgid "Done"
msgstr "Сделано" msgstr "Сделано"
@ -122,59 +105,63 @@ msgstr "Каталог, в который будут устанавливать
msgid "No such helper command" msgid "No such helper command"
msgstr "Такой вспомогательной команды нет" msgstr "Такой вспомогательной команды нет"
#: info.go:43 #: info.go:42
msgid "Print information about a package" msgid "Print information about a package"
msgstr "Отобразить информацию о пакете" msgstr "Отобразить информацию о пакете"
#: info.go:48 #: info.go:47
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "Показывать всю информацию, не только для текущего дистрибутива" msgstr "Показывать всю информацию, не только для текущего дистрибутива"
#: info.go:63 #: info.go:57
msgid "Error getting packages" msgid "Error initialization database"
msgstr "Ошибка при получении пакетов" msgstr "Ошибка инициализации базы данных"
#: info.go:72 #: info.go:64
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
#: info.go:93
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
#: info.go:107 #: info.go:78
msgid "Error finding packages" msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов" msgstr "Ошибка при поиске пакетов"
#: info.go:129 #: info.go:94
msgid "Error parsing os-release file" msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы" msgstr "Ошибка при разборе файла выпуска операционной системы"
#: info.go:138 #: info.go:103
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений" msgstr "Ошибка устранения переорпеделений"
#: info.go:147 info.go:153 #: info.go:112 info.go:118
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита" msgstr "Ошибка кодирования переменных скрита"
#: install.go:43 #: install.go:42
msgid "Install a new package" msgid "Install a new package"
msgstr "Установить новый пакет" msgstr "Установить новый пакет"
#: install.go:57 #: install.go:56
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
#: install.go:146 #: install.go:91
msgid "Error getting packages"
msgstr "Ошибка при получении пакетов"
#: install.go:100
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
#: install.go:113
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "Удалить установленный пакет" msgstr "Удалить установленный пакет"
#: install.go:151 #: install.go:118
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:163 #: install.go:130
msgid "Error removing packages" msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов" msgstr "Ошибка при удалении пакетов"
@ -202,56 +189,64 @@ msgstr "Выберите, какой пакет использовать для
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "Выберите, какой дополнительный пакет(ы) следует установить" msgstr "Выберите, какой дополнительный пакет(ы) следует установить"
#: internal/config/config.go:59 #: internal/config/config.go:64
msgid "Error opening config file, using defaults" msgid "Error opening config file, using defaults"
msgstr "" msgstr ""
"Ошибка при открытии конфигурационного файла, используются значения по " "Ошибка при открытии конфигурационного файла, используются значения по "
"умолчанию" "умолчанию"
#: internal/config/config.go:72 #: internal/config/config.go:77
msgid "Error decoding config file, using defaults" msgid "Error decoding config file, using defaults"
msgstr "" msgstr ""
"Ошибка при декодировании конфигурационного файла, используются значения по " "Ошибка при декодировании конфигурационного файла, используются значения по "
"умолчанию" "умолчанию"
#: internal/config/config.go:84 #: internal/config/config.go:89
msgid "Unable to detect user config directory" msgid "Unable to detect user config directory"
msgstr "Не удалось обнаружить каталог конфигурации пользователя" msgstr "Не удалось обнаружить каталог конфигурации пользователя"
#: internal/config/config.go:92 #: internal/config/config.go:97
msgid "Unable to create ALR config directory" msgid "Unable to create ALR config directory"
msgstr "Не удалось создать каталог конфигурации ALR" msgstr "Не удалось создать каталог конфигурации ALR"
#: internal/config/config.go:101 #: internal/config/config.go:106
msgid "Unable to create ALR config file" msgid "Unable to create ALR config file"
msgstr "Не удалось создать конфигурационный файл ALR" msgstr "Не удалось создать конфигурационный файл ALR"
#: internal/config/config.go:107 #: internal/config/config.go:112
msgid "Error encoding default configuration" msgid "Error encoding default configuration"
msgstr "Ошибка кодирования конфигурации по умолчанию" msgstr "Ошибка кодирования конфигурации по умолчанию"
#: internal/config/config.go:116 #: internal/config/config.go:121
msgid "Unable to detect cache directory" msgid "Unable to detect cache directory"
msgstr "Не удалось обнаружить каталог кэша" msgstr "Не удалось обнаружить каталог кэша"
#: internal/config/config.go:126 #: internal/config/config.go:131
msgid "Unable to create repo cache directory" msgid "Unable to create repo cache directory"
msgstr "Не удалось создать каталог кэша репозитория" msgstr "Не удалось создать каталог кэша репозитория"
#: internal/config/config.go:132 #: internal/config/config.go:137
msgid "Unable to create package cache directory" msgid "Unable to create package cache directory"
msgstr "Не удалось создать каталог кэша пакетов" msgstr "Не удалось создать каталог кэша пакетов"
#: internal/db/db.go:133 #: internal/config/lang.go:50
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:140 #: internal/db/db.go:135
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 "Исходный код можно обновлять, обновляя при необходимости"
@ -268,11 +263,11 @@ msgstr "Источник обновлён и связан с пунктом на
msgid "Downloading source" msgid "Downloading source"
msgstr "Скачивание источника" msgstr "Скачивание источника"
#: internal/dl/progress_tui.go:100 #: internal/dl/progress_tui.go:99
msgid "%s: done!\n" msgid "%s: done!\n"
msgstr "" msgstr ""
#: internal/dl/progress_tui.go:104 #: internal/dl/progress_tui.go:103
msgid "%s %s downloading at %s/s\n" msgid "%s %s downloading at %s/s\n"
msgstr "" msgstr ""
@ -288,19 +283,19 @@ msgstr "Список пакетов репозитория ALR"
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов" msgstr "Ошибка при составлении списка установленных пакетов"
#: main.go:44 #: main.go:45
msgid "Print the current ALR version and exit" msgid "Print the current ALR version and exit"
msgstr "Показать текущую версию ALR и выйти" msgstr "Показать текущую версию ALR и выйти"
#: main.go:60 #: main.go:61
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "Аргументы, которые будут переданы менеджеру пакетов" msgstr "Аргументы, которые будут переданы менеджеру пакетов"
#: main.go:66 #: main.go:67
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов" msgstr "Включение интерактивных вопросов и запросов"
#: main.go:91 #: main.go:90
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"
@ -308,31 +303,31 @@ msgstr ""
"Запуск ALR от имени root запрещён, так как это может привести к " "Запуск ALR от имени root запрещён, так как это может привести к "
"катастрофическому повреждению вашей системы" "катастрофическому повреждению вашей системы"
#: main.go:123 #: main.go:124
msgid "Error while running app" msgid "Error while running app"
msgstr "Ошибка при запуске приложения" msgstr "Ошибка при запуске приложения"
#: pkg/build/build.go:153 #: pkg/build/build.go:107
msgid "Failed to prompt user to view build script" msgid "Failed to prompt user to view build script"
msgstr "Не удалось предложить пользователю просмотреть скрипт сборки" msgstr "Не удалось предложить пользователю просмотреть скрипт сборки"
#: pkg/build/build.go:157 #: pkg/build/build.go:111
msgid "Building package" msgid "Building package"
msgstr "Сборка пакета" msgstr "Сборка пакета"
#: pkg/build/build.go:228 #: pkg/build/build.go:155
msgid "Downloading sources" msgid "Downloading sources"
msgstr "Скачивание источников" msgstr "Скачивание источников"
#: pkg/build/build.go:246 #: pkg/build/build.go:167
msgid "Building package metadata" msgid "Building package metadata"
msgstr "Сборка метаданных пакета" msgstr "Сборка метаданных пакета"
#: pkg/build/build.go:268 #: pkg/build/build.go:189
msgid "Compressing package" msgid "Compressing package"
msgstr "Сжатие пакета" msgstr "Сжатие пакета"
#: pkg/build/build.go:421 #: pkg/build/build.go:315
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?"
@ -340,46 +335,59 @@ msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все " "Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?" "равно хотите выполнить сборку?"
#: pkg/build/build.go:435 #: pkg/build/build.go:326
msgid "This package is already installed" msgid "This package is already installed"
msgstr "Этот пакет уже установлен" msgstr "Этот пакет уже установлен"
#: pkg/build/build.go:459 #: pkg/build/build.go:354
msgid "Installing build dependencies" msgid "Installing build dependencies"
msgstr "Установка зависимостей сборки" msgstr "Установка зависимостей сборки"
#: pkg/build/build.go:500 #: pkg/build/build.go:396
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "Установка зависимостей" msgstr "Установка зависимостей"
#: pkg/build/build.go:535 #: pkg/build/build.go:442
msgid "The checksums array must be the same length as sources" msgid "Executing version()"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники" msgstr "Исполнение версия()"
#: pkg/build/build.go:586 #: pkg/build/build.go:462
msgid "Would you like to remove the build dependencies?" msgid "Updating version"
msgstr "Хотели бы вы удалить зависимости сборки?" msgstr "Обновление версии"
#: pkg/build/build.go:649 #: pkg/build/build.go:467
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "Исполнение prepare()" msgstr "Исполнение prepare()"
#: pkg/build/build.go:659 #: pkg/build/build.go:477
msgid "Executing build()" msgid "Executing build()"
msgstr "Исполнение build()" msgstr "Исполнение build()"
#: pkg/build/build.go:689 pkg/build/build.go:709 #: pkg/build/build.go:489
#, fuzzy msgid "Executing package()"
msgid "Executing %s()" msgstr "Исполнение package()"
#: pkg/build/build.go:527
msgid "Executing files()"
msgstr "Исполнение files()" msgstr "Исполнение files()"
#: pkg/build/build.go:768 #: pkg/build/build.go:605
msgid "Error installing native packages" msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr "Ошибка при установке нативных пакетов" msgstr ""
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/build.go:792 #: pkg/build/build.go:616
msgid "Error installing package" msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr "Ошибка при установке пакета" msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/build.go:723
msgid "Would you like to remove the build dependencies?"
msgstr "Хотели бы вы удалить зависимости сборки?"
#: pkg/build/build.go:829
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"
@ -393,29 +401,27 @@ msgstr "Найденная предоставленная зависимость
msgid "Required dependency found" msgid "Required dependency found"
msgstr "Найдена требуемая зависимость" msgstr "Найдена требуемая зависимость"
#: pkg/build/utils.go:133 #: pkg/build/install.go:42
msgid "AutoProv is not implemented for this package format, so it's skipped" msgid "Error installing native packages"
msgstr "" msgstr "Ошибка при установке нативных пакетов"
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/utils.go:144 #: pkg/build/install.go:79
msgid "AutoReq is not implemented for this package format, so it's skipped" msgid "Error installing package"
msgstr "" msgstr "Ошибка при установке пакета"
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/repos/pull.go:79 #: pkg/repos/pull.go:75
msgid "Pulling repository" msgid "Pulling repository"
msgstr "Скачивание репозитория" msgstr "Скачивание репозитория"
#: pkg/repos/pull.go:103 #: pkg/repos/pull.go:99
msgid "Repository up to date" msgid "Repository up to date"
msgstr "Репозиторий уже обновлён" msgstr "Репозиторий уже обновлён"
#: pkg/repos/pull.go:160 #: pkg/repos/pull.go:156
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:176 #: pkg/repos/pull.go:172
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."
@ -435,96 +441,46 @@ msgstr "Название нового репозитория"
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория" msgstr "URL-адрес нового репозитория"
#: repo.go:82 repo.go:147 #: repo.go:79 repo.go:136
msgid "Error opening config file" msgid "Error opening config file"
msgstr "Ошибка при открытии конфигурационного файла" msgstr "Ошибка при открытии конфигурационного файла"
#: repo.go:88 repo.go:153 #: repo.go:85 repo.go:142
msgid "Error encoding config" msgid "Error encoding config"
msgstr "Ошибка при кодировании конфигурации" msgstr "Ошибка при кодировании конфигурации"
#: repo.go:113 #: repo.go:103
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий" msgstr "Удалить существующий репозиторий"
#: repo.go:120 #: repo.go:110
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён" msgstr "Название репозитория удалён"
#: repo.go:139 #: repo.go:128
msgid "Repo does not exist" msgid "Repo does not exist"
msgstr "Репозитория не существует" msgstr "Репозитория не существует"
#: repo.go:159 #: repo.go:148
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория" msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:170 #: repo.go:154
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных" msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:182 #: repo.go:166
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
#, fuzzy
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
#, fuzzy
msgid "Error parsing format template"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: search.go:107
#, fuzzy
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:90 #: upgrade.go:83
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr "Ошибка при проверке обновлений"
#: upgrade.go:112 #: upgrade.go:94
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,61 +23,11 @@ import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
type BuildOpts struct { type BuildOpts struct {
Script string Script 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 {

11
main.go

@ -32,6 +32,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"
"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"
@ -80,14 +81,12 @@ 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" && !cfg.AllowRunAsRoot(ctx) && os.Geteuid() == 0 { if cmd != "helper" && !config.Config(ctx).Unsafe.AllowRunAsRoot && 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)
} }
@ -99,6 +98,9 @@ func GetApp() *cli.App {
return nil return nil
}, },
After: func(ctx *cli.Context) error {
return db.Close()
},
EnableBashCompletion: true, EnableBashCompletion: true,
} }
} }
@ -108,12 +110,11 @@ 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 = cfg.RootCmd(ctx) manager.DefaultRootCmd = config.Config(ctx).RootCmd
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
defer cancel() defer cancel()

File diff suppressed because it is too large Load Diff

@ -18,18 +18,12 @@ package build
import ( import (
"context" "context"
"fmt"
"os"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "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"
) )
@ -142,145 +136,90 @@ func (m *TestManager) IsInstalled(pkg string) (bool, error) {
return true, nil return true, nil
} }
type TestConfig struct{} func TestInstallBuildDeps(t *testing.T) {
type testEnv struct {
pf PackageFinder
vars *types.BuildVars
opts types.BuildOpts
func (c *TestConfig) PagerStyle(ctx context.Context) string { // Contains pkgs captured by FindPkgsFunc
return "native" capturedPkgs []string
}
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
Script string Prepare func() *testEnv
Opts types.BuildOpts Expected func(t *testing.T, e *testEnv, res []string, err error)
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,
`,
Opts: types.BuildOpts{
Manager: &TestManager{},
Interactive: false, Interactive: false,
}
env := &testEnv{
pf: &pf,
vars: &vars,
opts: opts,
capturedPkgs: []string{},
}
pf.FindPkgsFunc = func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
env.capturedPkgs = append(env.capturedPkgs, pkgs...)
result := make(map[string][]db.Package)
result["bar"] = []db.Package{{
Name: "bar-pkg",
}}
result["buz"] = []db.Package{{
Name: "buz-pkg",
}}
return result, []string{}, nil
}
vars.BuildDepends = []string{
"foo",
"bar",
"buz",
}
m.IsInstalledFunc = func(pkg string) (bool, error) {
if pkg == "foo" {
return true, nil
} else {
return false, nil
}
}
return env
}, },
Expected: func(t *testing.T, vars []*types.BuildVars) { Expected: func(t *testing.T, e *testEnv, res []string, err error) {
assert.Equal(t, 1, len(vars)) assert.NoError(t, err)
assert.Equal(t, vars[0].Name, "test") assert.Len(t, res, 2)
assert.Equal(t, vars[0].Version, "1.0.0") assert.ElementsMatch(t, res, []string{"bar-pkg", "buz-pkg"})
assert.Equal(t, vars[0].Release, int(1))
assert.Equal(t, vars[0].Epoch, uint(2)) assert.ElementsMatch(t, e.capturedPkgs, []string{"bar", "buz"})
assert.Equal(t, vars[0].Description, "Test package")
}, },
}, {
Name: "multiple packages",
Script: `name=(
foo
bar
)
version='0.0.1'
release=1
epoch=2
desc="Test package"
meta_foo() {
desc="Foo package"
}
meta_bar() {
}
`,
Opts: types.BuildOpts{
Packages: []string{"foo"},
Manager: &TestManager{},
Interactive: false,
}, },
Expected: func(t *testing.T, vars []*types.BuildVars) { } {
assert.Equal(t, 1, len(vars)) t.Run(tc.Name, func(tt *testing.T) {
assert.Equal(t, vars[0].Name, "foo")
assert.Equal(t, vars[0].Description, "Foo package")
},
}} {
t.Run(tc.Name, func(t *testing.T) {
cfg := &TestConfig{}
pf := &TestPackageFinder{}
info := &distro.OSRelease{}
ctx := context.Background() ctx := context.Background()
env := tc.Prepare()
b := NewBuilder( result, err := installBuildDeps(
ctx, ctx,
tc.Opts, env.pf,
pf, env.vars,
info, env.opts,
cfg,
) )
fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh") tc.Expected(tt, env, result, err)
assert.NoError(t, err)
_, allVars, err := b.executeFirstPass(fl)
assert.NoError(t, err)
tc.Expected(t, allVars)
}) })
} }
} }

84
pkg/build/install.go Normal file

@ -0,0 +1,84 @@
// 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)
// Логируем и завершаем выполнение при ошибке установки
}
}
}

@ -1,423 +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 build
import (
"context"
"io"
"log/slog"
"os"
"path/filepath"
"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"
"github.com/leonelquinteros/gotext"
"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/shutils/decoder"
"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) // Создаем директорию для пакетов
}
// Функция buildPkgMetadata создает метаданные для пакета, который будет собран.
func buildPkgMetadata(
ctx context.Context,
vars *types.BuildVars,
dirs types.Directories,
pkgFormat string,
info *distro.OSRelease,
deps []string,
preferedContents *[]string,
) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars, info)
pkgInfo.Description = vars.Description
pkgInfo.Platform = "linux"
pkgInfo.Homepage = vars.Homepage
pkgInfo.License = strings.Join(vars.Licenses, ", ")
pkgInfo.Maintainer = vars.Maintainer
pkgInfo.Overridables = nfpm.Overridables{
Conflicts: vars.Conflicts,
Replaces: vars.Replaces,
Provides: vars.Provides,
Depends: deps,
}
if pkgFormat == "apk" {
// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы
pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
return s == pkgInfo.Name
})
}
if vars.Epoch != 0 {
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
}
setScripts(vars, pkgInfo, dirs.ScriptDir)
if slices.Contains(vars.Architectures, "all") {
pkgInfo.Arch = "all"
}
contents, err := buildContents(vars, dirs, preferedContents)
if err != nil {
return nil, err
}
pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) {
if pkgFormat == "rpm" {
err = rpmFindProvides(ctx, pkgInfo, dirs)
if err != nil {
return nil, err
}
} else {
slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped"))
}
}
if len(vars.AutoReq) == 1 && decoder.IsTruthy(vars.AutoReq[0]) {
if pkgFormat == "rpm" {
err = rpmFindRequires(ctx, pkgInfo, dirs)
if err != nil {
return nil, err
}
} else {
slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped"))
}
}
return pkgInfo, nil
}
// Функция 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
}
// Функция checkForBuiltPackage пытается обнаружить ранее собранный пакет и вернуть его путь
// и true, если нашла. Если нет, возвратит "", false, nil.
func checkForBuiltPackage(
mgr manager.Manager,
vars *types.BuildVars,
pkgFormat,
baseDir string,
info *distro.OSRelease,
) (string, bool, error) {
filename, err := pkgFileName(vars, pkgFormat, info)
if err != nil {
return "", false, err
}
pkgPath := filepath.Join(baseDir, filename)
_, err = os.Stat(pkgPath)
if err != nil {
return "", false, nil
}
return pkgPath, true, nil
}
func getBasePkgInfo(vars *types.BuildVars, info *distro.OSRelease) *nfpm.Info {
return &nfpm.Info{
Name: vars.Name,
Arch: cpu.Arch(),
Version: vars.Version,
Release: overrides.ReleasePlatformSpecific(vars.Release, info),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
}
}
// pkgFileName returns the filename of the package if it were to be built.
// This is used to check if the package has already been built.
func pkgFileName(vars *types.BuildVars, pkgFormat string, info *distro.OSRelease) (string, error) {
pkgInfo := getBasePkgInfo(vars, info)
packager, err := nfpm.Get(pkgFormat)
if err != nil {
return "", err
}
return packager.ConventionalFileName(pkgInfo), nil
}
// Функция 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
}

@ -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.ReadDirHandler2(handlers.NopReadDir), interp.ReadDirHandler(handlers.NopReadDir),
interp.StatHandler(handlers.NopStat), interp.StatHandler(handlers.NopStat),
interp.Env(expand.ListEnviron()), interp.Env(expand.ListEnviron()),
) )

@ -22,8 +22,6 @@ package repos
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io"
"log/slog" "log/slog"
"net/url" "net/url"
"os" "os"
@ -43,10 +41,8 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/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
@ -77,7 +73,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(rs.cfg.GetPaths(ctx).RepoDir, repo.Name) repoDir := filepath.Join(config.GetPaths(ctx).RepoDir, repo.Name)
var repoFS billy.Filesystem var repoFS billy.Filesystem
gitDir := filepath.Join(repoDir, ".git") gitDir := filepath.Join(repoDir, ".git")
@ -181,96 +177,6 @@ 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 {
@ -295,18 +201,18 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
continue continue
} }
switch { if to == nil {
case to == nil:
actions = append(actions, action{ actions = append(actions, action{
Type: actionDelete, Type: actionDelete,
File: from.Path(), File: from.Path(),
}) })
case from == nil: } else if from == nil {
actions = append(actions, action{ actions = append(actions, action{
Type: actionUpdate, Type: actionUpdate,
File: to.Path(), File: to.Path(),
}) })
case from.Path() != to.Path(): } else {
if from.Path() != to.Path() {
actions = append(actions, actions = append(actions,
action{ action{
Type: actionDelete, Type: actionDelete,
@ -317,19 +223,28 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
File: to.Path(), File: to.Path(),
}, },
) )
default: } else {
actions = append(actions, action{ actions = append(actions, action{
Type: actionUpdate, Type: actionUpdate,
File: to.Path(), File: to.Path(),
}) })
} }
} }
}
repoDir := w.Filesystem.Root() repoDir := w.Filesystem.Root()
parser := syntax.NewParser() parser := syntax.NewParser()
for _, action := range actions { for _, action := range actions {
runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(filepath.Join(repoDir, action.File))) env := append(os.Environ(), "scriptdir="+filepath.Dir(filepath.Join(repoDir, action.File)))
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
)
if err != nil { if err != nil {
return err return err
} }
@ -375,7 +290,23 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
return nil return nil
} }
err = rs.updatePkg(ctx, repo, runner, r) pkg := db.Package{
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repo.Name,
}
err = parseScript(ctx, parser, runner, r, &pkg)
if err != nil {
return err
}
resolveOverrides(runner, &pkg)
err = rs.db.InsertPackage(ctx, pkg)
if err != nil { if err != nil {
return err return err
} }
@ -392,8 +323,18 @@ 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 {
runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) env := append(os.Environ(), "scriptdir="+filepath.Dir(match))
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
)
if err != nil { if err != nil {
return err return err
} }
@ -403,7 +344,23 @@ func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir s
return err return err
} }
err = rs.updatePkg(ctx, repo, runner, scriptFl) pkg := db.Package{
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repo.Name,
}
err = parseScript(ctx, parser, runner, scriptFl, &pkg)
if err != nil {
return err
}
resolveOverrides(runner, &pkg)
err = rs.db.InsertPackage(ctx, pkg)
if err != nil { if err != nil {
return err return err
} }

@ -1,173 +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"
"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)
})
}
}

70
pkg/repos/repos_legacy.go Normal file

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

@ -1,86 +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 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
}

@ -1,65 +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 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)
})
}
}

47
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"
database "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/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
@ -60,24 +60,21 @@ func AddRepoCmd() *cli.Command {
name := c.String("name") name := c.String("name")
repoURL := c.String("url") repoURL := c.String("url")
cfg := config.New() cfg := config.Config(ctx)
reposSlice := cfg.Repos(ctx)
for _, repo := range reposSlice { for _, repo := range cfg.Repos {
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)
} }
} }
reposSlice = append(reposSlice, types.Repo{ cfg.Repos = append(cfg.Repos, types.Repo{
Name: name, Name: name,
URL: repoURL, URL: repoURL,
}) })
cfg.SetRepos(ctx, reposSlice) 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)
@ -89,14 +86,7 @@ func AddRepoCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
db := database.New(cfg) err = repos.Pull(ctx, cfg.Repos)
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)
@ -124,12 +114,11 @@ func RemoveRepoCmd() *cli.Command {
ctx := c.Context ctx := c.Context
name := c.String("name") name := c.String("name")
cfg := config.New() cfg := config.Config(ctx)
found := false found := false
index := 0 index := 0
reposSlice := cfg.Repos(ctx) for i, repo := range cfg.Repos {
for i, repo := range reposSlice {
if repo.Name == name { if repo.Name == name {
index = i index = i
found = true found = true
@ -140,9 +129,9 @@ func RemoveRepoCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
cfg.SetRepos(ctx, slices.Delete(reposSlice, index, index+1)) cfg.Repos = slices.Delete(cfg.Repos, index, index+1)
cfgFl, err := os.Create(cfg.GetPaths(ctx).ConfigPath) cfgFl, err := os.Create(config.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)
@ -154,17 +143,12 @@ func RemoveRepoCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
err = os.RemoveAll(filepath.Join(cfg.GetPaths(ctx).RepoDir, name)) err = os.RemoveAll(filepath.Join(config.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)
@ -183,14 +167,7 @@ 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
cfg := config.New() err := repos.Pull(ctx, config.Config(ctx).Repos)
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)

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

@ -78,10 +78,6 @@ 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
@ -102,10 +98,7 @@ 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' | grep -v 'alt1' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.x86_64.rpm' | 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

@ -1,119 +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 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
},
}
}

@ -32,7 +32,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"
database "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/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"
@ -56,14 +56,7 @@ func UpgradeCmd() *cli.Command {
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
cfg := config.New() cfg := config.GetInstance(ctx)
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 {
@ -78,32 +71,21 @@ func UpgradeCmd() *cli.Command {
} }
if cfg.AutoPull(ctx) { if cfg.AutoPull(ctx) {
err = rs.Pull(ctx, cfg.Repos(ctx)) err = repos.Pull(ctx, config.Config(ctx).Repos)
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, cfg, rs, info) updates, err := checkForUpdates(ctx, mgr, 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 {
builder := build.NewBuilder( build.InstallPkgs(ctx, updates, nil, types.BuildOpts{
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"),
@ -117,33 +99,27 @@ func UpgradeCmd() *cli.Command {
} }
} }
func checkForUpdates( func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) {
ctx context.Context,
mgr manager.Manager,
cfg *config.ALRConfig,
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 := rs.FindPkgs(ctx, pkgNames) found, _, err := repos.FindPkgs(ctx, pkgNames)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var out []database.Package var out []db.Package
for pkgName, pkgs := range found { for pkgName, pkgs := range found {
if slices.Contains(cfg.IgnorePkgUpdates(ctx), pkgName) { if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkgName) {
continue continue
} }
if len(pkgs) > 1 { if len(pkgs) > 1 {
// Puts the element with the highest version first // Puts the element with the highest version first
slices.SortFunc(pkgs, func(a, b database.Package) int { slices.SortFunc(pkgs, func(a, b db.Package) int {
return vercmp.Compare(a.Version, b.Version) return vercmp.Compare(a.Version, b.Version)
}) })
} }