4 Commits

Author SHA1 Message Date
083df3c7aa wip 2025-02-11 12:59:10 +03:00
100739c419 Merge remote-tracking branch 'upstream/master' into wip-build-multiple-packages 2025-02-11 12:25:36 +03:00
17bbb2f4c5 wip 2025-02-11 12:24:57 +03:00
8978cc2855 wip: add split packages support 2025-02-03 19:15:54 +03:00
65 changed files with 1262 additions and 2408 deletions

2
.gitignore vendored
View File

@@ -8,5 +8,3 @@
.gigaide .gigaide
*.out *.out
e2e-tests/alr

View File

@@ -47,4 +47,4 @@ issues:
# TODO: remove # TODO: remove
- linters: - linters:
- staticcheck - staticcheck
text: "SA1019: interp.ExecHandler" text: "SA1019:"

View File

@@ -21,7 +21,7 @@ build: check-no-root $(BIN)
export CGO_ENABLED := 0 export CGO_ENABLED := 0
$(BIN): $(BIN):
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@ go build -ldflags="-X 'gitea.plemya-x.ru/xpamych/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
check-no-root: check-no-root:
@if [[ "$$(whoami)" == 'root' ]]; then \ @if [[ "$$(whoami)" == 'root' ]]; then \
@@ -66,13 +66,7 @@ i18n:
$(XGOTEXT_BIN) --output ./internal/translations/default.pot $(XGOTEXT_BIN) --output ./internal/translations/default.pot
msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot
msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot
bash scripts/i18n-badge.sh
test-coverage: test-coverage:
go test ./... -v -coverpkg=./... -coverprofile=coverage.out go test ./... -v -coverpkg=./... -coverprofile=coverage.out
bash scripts/coverage-badge.sh bash scripts/coverage-badge.sh
e2e-test: clean build
rm -f ./e2e-tests/alr
cp alr e2e-tests
go test -tags=e2e ./...

View File

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

View File

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

Before

Width:  |  Height:  |  Size: 940 B

View File

@@ -50,9 +50,9 @@ func BuildCmd() *cli.Command {
Usage: gotext.Get("Path to the build script"), Usage: gotext.Get("Path to the build script"),
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "subpackage", Name: "script-package",
Aliases: []string{"sb"}, Aliases: []string{"sp"},
Usage: gotext.Get("Specify subpackage in script (for multi package script only)"), Usage: gotext.Get("Specify package in script (for multi package script only)"),
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "package", Name: "package",
@@ -68,30 +68,24 @@ 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() cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg) db := database.New(cfg)
rs := repos.New(cfg, db) rs := repos.New(cfg, db)
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 db init"), "err", err)
os.Exit(1) os.Exit(1)
} }
var script string var script, packageName string
var packages []string
repository := "default"
repoDir := cfg.GetPaths().RepoDir // Проверяем, установлен ли флаг script (-s)
repoDir := cfg.GetPaths(ctx).RepoDir
switch { switch {
case c.IsSet("script"): case c.IsSet("script"):
script = c.String("script") script = c.String("script")
packages = append(packages, c.String("script-package")) packageName = c.String("script-package")
case c.IsSet("package"): case c.IsSet("package"):
// TODO: handle multiple packages // TODO: handle multiple packages
packageInput := c.String("package") packageInput := c.String("package")
@@ -111,21 +105,19 @@ func BuildCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
repository = pkg[0].Repository
if pkg[0].BasePkgName != "" { if pkg[0].BasePkgName != "" {
script = filepath.Join(repoDir, repository, pkg[0].BasePkgName, "alr.sh") script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].BasePkgName, "alr.sh")
packages = append(packages, pkg[0].Name) packageName = pkg[0].Name
} else { } else {
script = filepath.Join(repoDir, repository, pkg[0].Name, "alr.sh") script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].Name, "alr.sh")
} }
default: default:
script = filepath.Join(repoDir, "alr.sh") script = filepath.Join(repoDir, "alr.sh")
} }
// Проверка автоматического пулла репозиториев // Проверка автоматического пулла репозиториев
if cfg.AutoPull() { if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos()) err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err) slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1) os.Exit(1)
@@ -148,8 +140,7 @@ func BuildCmd() *cli.Command {
builder := build.NewBuilder( builder := build.NewBuilder(
ctx, ctx,
types.BuildOpts{ types.BuildOpts{
Packages: packages, Package: packageName,
Repository: repository,
Script: script, Script: script,
Manager: mgr, Manager: mgr,
Clean: c.Bool("clean"), Clean: c.Bool("clean"),

View File

@@ -11,7 +11,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text> <text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.4%</text> <text x="86" y="15" fill="#010101" fill-opacity=".3">20.8%</text>
<text x="86" y="14">19.4%</text> <text x="86" y="14">20.8%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

View File

@@ -1,70 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"bytes"
"testing"
"github.com/efficientgo/e2e"
"github.com/stretchr/testify/assert"
)
func TestE2EAlrAddRepo(t *testing.T) {
dockerMultipleRun(
t,
"add-repo-remove-repo",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
"cat $HOME/.config/alr/alr.toml",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"alr",
"removerepo",
"--name",
"alr-repo",
))
assert.NoError(t, err)
var buf bytes.Buffer
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
"cat $HOME/.config/alr/alr.toml",
), e2e.WithExecOptionStdout(&buf))
assert.NoError(t, err)
assert.Contains(t, buf.String(), "rootCmd")
},
)
}

View File

@@ -1,180 +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/>.
//go:build e2e
package e2etests_test
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"log"
"os"
"os/exec"
"testing"
"time"
"github.com/efficientgo/e2e"
"github.com/stretchr/testify/assert"
expect "github.com/tailscale/goexpect"
)
// DebugWriter оборачивает io.Writer и логирует все записываемые данные.
type DebugWriter struct {
prefix string
writer io.Writer
}
func (d *DebugWriter) Write(p []byte) (n int, err error) {
log.Printf("%s: Writing data: %q", d.prefix, p) // Логируем данные
return d.writer.Write(p)
}
// DebugReader оборачивает io.Reader и логирует все читаемые данные.
type DebugReader struct {
prefix string
reader io.Reader
}
func (d *DebugReader) Read(p []byte) (n int, err error) {
n, err = d.reader.Read(p)
if n > 0 {
log.Printf("%s: Read data: %q", d.prefix, p[:n]) // Логируем данные
}
return n, err
}
func e2eSpawn(runnable e2e.Runnable, command e2e.Command, timeout time.Duration, opts ...expect.Option) (expect.Expecter, <-chan error, error, *io.PipeWriter) {
resCh := make(chan error)
// Создаем pipe для stdin и stdout
stdinReader, stdinWriter := io.Pipe()
stdoutReader, stdoutWriter := io.Pipe()
debugStdinReader := &DebugReader{prefix: "STDIN", reader: stdinReader}
debugStdoutWriter := &DebugWriter{prefix: "STDOUT", writer: stdoutWriter}
go func() {
err := runnable.Exec(
command,
e2e.WithExecOptionStdout(debugStdoutWriter),
e2e.WithExecOptionStdin(debugStdinReader),
e2e.WithExecOptionStderr(debugStdoutWriter),
)
resCh <- err
}()
exp, chnErr, err := expect.SpawnGeneric(&expect.GenOptions{
In: stdinWriter,
Out: stdoutReader,
Wait: func() error {
return <-resCh
},
Close: func() error {
stdinWriter.Close()
stdoutReader.Close()
return nil
},
Check: func() bool { return true },
}, timeout, expect.Verbose(true), expect.VerboseWriter(os.Stdout))
return exp, chnErr, err, stdinWriter
}
var ALL_SYSTEMS []string = []string{
"ubuntu-24.04",
// "alt-sisyphus",
// "archlinux",
// "alpine",
// "opensuse-leap",
// "redos-8",
}
var COMMON_SYSTEMS []string = []string{
"ubuntu-24.04",
}
func init() {
for _, id := range ALL_SYSTEMS {
buildAlrTestImage(id)
}
}
func buildAlrTestImage(id string) {
cmd := exec.Command(
"docker",
"build",
"-t", fmt.Sprintf("alr-testimage-%s", id),
"-f", fmt.Sprintf("images/Dockerfile.%s", id),
".",
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Println("Error:", err)
return
}
}
func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testing.T, runnable e2e.Runnable)) {
t.Run(name, func(t *testing.T) {
for _, id := range ids {
t.Run(id, func(t *testing.T) {
t.Parallel()
dockerName := fmt.Sprintf("alr-test-%s-%s", name, id)
hash := sha256.New()
hash.Write([]byte(dockerName))
hashSum := hash.Sum(nil)
hashString := hex.EncodeToString(hashSum)
truncatedHash := hashString[:8]
e, err := e2e.New(e2e.WithVerbose(), e2e.WithName(fmt.Sprintf("alr-%s", truncatedHash)))
assert.NoError(t, err)
t.Cleanup(e.Close)
imageId := fmt.Sprintf("alr-testimage-%s", id)
runnable := e.Runnable(dockerName).Init(
e2e.StartOptions{
Image: imageId,
Volumes: []string{
"./alr:/usr/bin/alr",
},
Privileged: true,
},
)
assert.NoError(t, e2e.StartAndWaitReady(runnable))
f(t, runnable)
})
}
})
}
func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) {
exp, _, err, _ := e2eSpawn(
r,
e2e.NewCommand("/bin/bash"), 25*time.Second,
expect.Verbose(true),
)
assert.NoError(t, err)
_, err = exp.ExpectBatch(
expects,
timeout,
)
assert.NoError(t, err)
}

View File

@@ -1,43 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"time"
"github.com/efficientgo/e2e"
expect "github.com/tailscale/goexpect"
)
func TestE2EAlrFix(t *testing.T) {
dockerMultipleRun(
t,
"run-fix",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
runTestCommands(t, r, time.Second*30, []expect.Batcher{
&expect.BSnd{S: "alr fix\n"},
&expect.BExp{R: `--> Done`},
&expect.BSnd{S: "echo $?\n"},
&expect.BExp{R: `^0\n$`},
})
},
)
}

View File

@@ -1,4 +0,0 @@
FROM alpine:latest
RUN adduser -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@@ -1,5 +0,0 @@
FROM registry.altlinux.org/sisyphus/alt:latest
RUN apt-get update && apt-get install -y ca-certificates
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@@ -1,4 +0,0 @@
FROM archlinux:latest
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@@ -1,4 +0,0 @@
FROM opensuse/leap:latest
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@@ -1,4 +0,0 @@
FROM registry.red-soft.ru/ubi8/ubi:latest
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@@ -1,7 +0,0 @@
FROM ubuntu:24.10
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates sudo
RUN useradd -m -s /bin/bash alr-user && \
echo "alr-user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/alr-user && \
chmod 0440 /etc/sudoers.d/alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@@ -1,40 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
func TestE2EIssue32Interactive(t *testing.T) {
dockerMultipleRun(
t,
"issue-32-interactive",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"alr", "--interactive=false", "remove", "ca-certificates",
))
assert.NoError(t, err)
},
)
}

View File

@@ -1,55 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
func TestE2EIssue50InstallMultiple(t *testing.T) {
dockerMultipleRun(
t,
"issue-50-install-multiple",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"alr", "in", "foo-pkg", "bar-pkg",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("cat", "/opt/foo"))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("cat", "/opt/bar"))
assert.NoError(t, err)
},
)
}

View File

@@ -1,44 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"time"
"github.com/efficientgo/e2e"
expect "github.com/tailscale/goexpect"
)
func TestE2EAlrVersion(t *testing.T) {
dockerMultipleRun(
t,
"check-version",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
runTestCommands(t, r, time.Second*10, []expect.Batcher{
&expect.BSnd{S: "alr version\n"},
&expect.BExp{R: `^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`},
&expect.BSnd{S: "echo $?\n"},
&expect.BExp{R: `^0\n$`},
})
},
)
}

29
fix.go
View File

@@ -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,18 +37,13 @@ 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()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
paths := cfg.GetPaths() db.Close()
paths := config.GetPaths(ctx)
slog.Info(gotext.Get("Removing cache directory")) slog.Info(gotext.Get("Removing cache directory"))
err = os.RemoveAll(paths.CacheDir) err := os.RemoveAll(paths.CacheDir)
if err != nil { if err != nil {
slog.Error(gotext.Get("Unable to remove cache directory"), "err", err) slog.Error(gotext.Get("Unable to remove cache directory"), "err", err)
os.Exit(1) os.Exit(1)
@@ -62,21 +57,7 @@ func FixCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
cfg = config.New() err = repos.Pull(ctx, config.Config(ctx).Repos)
err = cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
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())
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)

8
go.mod
View File

@@ -8,14 +8,11 @@ require (
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1 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/assert/v2 v2.2.1
github.com/alecthomas/chroma/v2 v2.9.1 github.com/alecthomas/chroma/v2 v2.9.1
github.com/caarlos0/env v3.5.0+incompatible
github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.2.4 github.com/charmbracelet/bubbletea v1.2.4
github.com/charmbracelet/lipgloss v1.0.0 github.com/charmbracelet/lipgloss v1.0.0
github.com/charmbracelet/log v0.4.0 github.com/charmbracelet/log v0.4.0
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
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/google/shlex v0.0.0-20191202100458-e7afc7fbc510
@@ -29,7 +26,6 @@ require (
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/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/vmihailenco/msgpack/v5 v5.3.5
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
@@ -50,7 +46,6 @@ require (
github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/alecthomas/repr v0.2.0 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect github.com/andybalholm/brotli v1.0.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
@@ -69,7 +64,6 @@ require (
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/efficientgo/core v1.0.0-rc.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
@@ -77,14 +71,12 @@ require (
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
github.com/google/uuid v1.4.0 // indirect github.com/google/uuid v1.4.0 // indirect
github.com/goreleaser/chglog v0.6.1 // indirect github.com/goreleaser/chglog v0.6.1 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.3.3 // indirect github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect github.com/imdario/mergo v0.3.16 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect

37
go.sum
View File

@@ -61,8 +61,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
@@ -72,15 +70,11 @@ github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzO
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA= github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
@@ -121,10 +115,6 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/efficientgo/core v1.0.0-rc.0 h1:jJoA0N+C4/knWYVZ6GrdHOtDyrg8Y/TR4vFpTaqTsqs=
github.com/efficientgo/core v1.0.0-rc.0/go.mod h1:kQa0V74HNYMfuJH6jiPiwNdpWXl4xd/K4tzlrcvYDQI=
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 h1:C/FNIs+MtAJgQYLJ9FX/ACFYyDRuLYoXTmueErrOJyA=
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0/go.mod h1:plsKU0YHE9uX+7utvr7SiDtVBSHJyEfHRO4UnUgDmts=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
@@ -171,8 +161,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -183,8 +171,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -231,8 +217,6 @@ github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3m
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@@ -278,8 +262,6 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
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=
@@ -300,8 +282,6 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
@@ -316,15 +296,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo=
github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -366,8 +338,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
@@ -465,8 +435,6 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -572,8 +540,6 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -594,8 +560,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -606,7 +570,6 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

62
info.go
View File

@@ -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,52 +47,12 @@ 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()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
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()
err := cfg.Load() db := db.New(cfg)
if err != nil { err := db.Init(ctx)
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg)
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)
os.Exit(1) os.Exit(1)
@@ -106,8 +65,8 @@ func InfoCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
if cfg.AutoPull() { if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos()) err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err) slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1) os.Exit(1)
@@ -129,15 +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 systemLang == "" {
systemLang = "en"
}
if !all { if !all {
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
@@ -147,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)

View File

@@ -46,7 +46,7 @@ func InstallCmd() *cli.Command {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "clean", Name: "clean",
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: gotext.Get("Build package from scratch even if there's an already built package available"), Usage: "Build package from scratch even if there's an already built package available",
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
@@ -65,22 +65,16 @@ func InstallCmd() *cli.Command {
} }
cfg := config.New() cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg) db := database.New(cfg)
rs := repos.New(cfg, db) rs := repos.New(cfg, db)
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 db init"), "err", err)
os.Exit(1) os.Exit(1)
} }
if cfg.AutoPull() { if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos()) err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err) slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1) os.Exit(1)
@@ -125,11 +119,6 @@ func InstallCmd() *cli.Command {
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {
cfg := config.New() cfg := config.New()
db := database.New(cfg) db := database.New(cfg)
err := db.Init(c.Context)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
}
result, err := db.GetPkgs(c.Context, "true") result, err := db.GetPkgs(c.Context, "true")
if err != nil { if err != nil {
slog.Error(gotext.Get("Error getting packages"), "err", err) slog.Error(gotext.Get("Error getting packages"), "err", err)
@@ -169,10 +158,7 @@ func RemoveCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
err := mgr.Remove(&manager.Opts{ err := mgr.Remove(nil, c.Args().Slice()...)
AsRoot: true,
NoConfirm: !c.Bool("interactive"),
}, c.Args().Slice()...)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error removing packages"), "err", err) slog.Error(gotext.Get("Error removing packages"), "err", err)
os.Exit(1) os.Exit(1)

View File

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

View File

@@ -20,21 +20,25 @@
package config package config
import ( import (
"context"
"log/slog" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "sync"
"github.com/caarlos0/env"
"github.com/leonelquinteros/gotext"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
type ALRConfig struct { type ALRConfig struct {
cfg *types.Config cfg *types.Config
paths *Paths paths *Paths
cfgOnce sync.Once
pathsOnce sync.Once
} }
var defaultConfig = &types.Config{ var defaultConfig = &types.Config{
@@ -42,159 +46,134 @@ var defaultConfig = &types.Config{
PagerStyle: "native", PagerStyle: "native",
IgnorePkgUpdates: []string{}, IgnorePkgUpdates: []string{},
AutoPull: true, 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 {
return &ALRConfig{} return &ALRConfig{}
} }
func readConfig(path string) (*types.Config, error) { func (c *ALRConfig) Load(ctx context.Context) {
file, err := os.Open(path) cfgFl, err := os.Open(c.GetPaths(ctx).ConfigPath)
if err != nil { if err != nil {
return nil, err slog.Warn(gotext.Get("Error opening config file, using defaults"), "err", err)
} c.cfg = defaultConfig
defer file.Close()
config := types.Config{}
if err := toml.NewDecoder(file).Decode(&config); err != nil {
return nil, err
}
return &config, nil
}
func mergeStructs(dst, src interface{}) {
srcVal := reflect.ValueOf(src)
if srcVal.IsNil() {
return return
} }
srcVal = srcVal.Elem() defer cfgFl.Close()
dstVal := reflect.ValueOf(dst).Elem()
for i := range srcVal.NumField() { // Copy the default configuration into config
srcField := srcVal.Field(i) defCopy := *defaultConfig
srcFieldName := srcVal.Type().Field(i).Name config := &defCopy
config.Repos = nil
dstField := dstVal.FieldByName(srcFieldName) err = toml.NewDecoder(cfgFl).Decode(config)
if dstField.IsValid() && dstField.CanSet() {
dstField.Set(srcField)
}
}
}
const systemConfigPath = "/etc/alr/alr.toml"
func (c *ALRConfig) Load() error {
systemConfig, err := readConfig(
systemConfigPath,
)
if err != nil { if err != nil {
slog.Debug("Cannot read system config", "err", err) slog.Warn(gotext.Get("Error decoding config file, using defaults"), "err", err)
c.cfg = defaultConfig
return
} }
cfgDir, err := os.UserConfigDir()
if err != nil {
slog.Debug("Cannot read user config directory")
}
userConfigPath := filepath.Join(cfgDir, "alr", "alr.toml")
userConfig, err := readConfig(
userConfigPath,
)
if err != nil {
slog.Debug("Cannot read user config")
}
config := &types.Config{}
mergeStructs(config, defaultConfig)
mergeStructs(config, systemConfig)
mergeStructs(config, userConfig)
err = env.Parse(config)
if err != nil {
return err
}
c.cfg = config c.cfg = config
cacheDir, err := os.UserCacheDir()
if err != nil {
return err
}
c.paths = &Paths{}
c.paths.UserConfigPath = userConfigPath
c.paths.CacheDir = filepath.Join(cacheDir, "alr")
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs")
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db")
c.initPaths()
return nil
}
func (c *ALRConfig) RootCmd() string {
return c.cfg.RootCmd
}
func (c *ALRConfig) PagerStyle() string {
return c.cfg.PagerStyle
}
func (c *ALRConfig) AutoPull() bool {
return c.cfg.AutoPull
}
func (c *ALRConfig) AllowRunAsRoot() bool {
return c.cfg.Unsafe.AllowRunAsRoot
}
func (c *ALRConfig) Repos() []types.Repo {
return c.cfg.Repos
}
func (c *ALRConfig) SetRepos(repos []types.Repo) {
c.cfg.Repos = repos
}
func (c *ALRConfig) IgnorePkgUpdates() []string {
return c.cfg.IgnorePkgUpdates
}
func (c *ALRConfig) LogLevel() string {
return c.cfg.LogLevel
}
func (c *ALRConfig) GetPaths() *Paths {
return c.paths
} }
func (c *ALRConfig) initPaths() { func (c *ALRConfig) initPaths() {
err := os.MkdirAll(filepath.Dir(c.paths.UserConfigPath), 0o755) paths := &Paths{}
cfgDir, err := os.UserConfigDir()
if err != nil { if err != nil {
slog.Error(gotext.Get("Unable to create config directory"), "err", err) slog.Error(gotext.Get("Unable to detect user config directory"), "err", err)
os.Exit(1) os.Exit(1)
} }
err = os.MkdirAll(c.paths.RepoDir, 0o755) paths.ConfigDir = filepath.Join(cfgDir, "alr")
err = os.MkdirAll(paths.ConfigDir, 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create ALR config directory"), "err", err)
os.Exit(1)
}
paths.ConfigPath = filepath.Join(paths.ConfigDir, "alr.toml")
if _, err := os.Stat(paths.ConfigPath); err != nil {
cfgFl, err := os.Create(paths.ConfigPath)
if err != nil {
slog.Error(gotext.Get("Unable to create ALR config file"), "err", err)
os.Exit(1)
}
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
if err != nil {
slog.Error(gotext.Get("Error encoding default configuration"), "err", err)
os.Exit(1)
}
cfgFl.Close()
}
cacheDir, err := os.UserCacheDir()
if err != nil {
slog.Error(gotext.Get("Unable to detect cache directory"), "err", err)
os.Exit(1)
}
paths.CacheDir = filepath.Join(cacheDir, "alr")
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
err = os.MkdirAll(paths.RepoDir, 0o755)
if err != nil { if err != nil {
slog.Error(gotext.Get("Unable to create repo cache directory"), "err", err) slog.Error(gotext.Get("Unable to create repo cache directory"), "err", err)
os.Exit(1) os.Exit(1)
} }
err = os.MkdirAll(c.paths.PkgsDir, 0o755) err = os.MkdirAll(paths.PkgsDir, 0o755)
if err != nil { if err != nil {
slog.Error(gotext.Get("Unable to create package cache directory"), "err", err) slog.Error(gotext.Get("Unable to create package cache directory"), "err", err)
os.Exit(1) os.Exit(1)
} }
paths.DBPath = filepath.Join(paths.CacheDir, "db")
c.paths = paths
} }
func (c *ALRConfig) SaveUserConfig() error { func (c *ALRConfig) GetPaths(ctx context.Context) *Paths {
f, err := os.Create(c.paths.UserConfigPath) c.pathsOnce.Do(func() {
if err != nil { c.initPaths()
return err })
} return c.paths
}
return toml.NewEncoder(f).Encode(c.cfg)
func (c *ALRConfig) Repos(ctx context.Context) []types.Repo {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.Repos
}
func (c *ALRConfig) IgnorePkgUpdates(ctx context.Context) []string {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.IgnorePkgUpdates
}
func (c *ALRConfig) AutoPull(ctx context.Context) bool {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.AutoPull
}
func (c *ALRConfig) PagerStyle(ctx context.Context) string {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.PagerStyle
} }

View File

@@ -14,39 +14,39 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e package config
package e2etests_test
import ( import (
"testing" "context"
"sync"
"github.com/alecthomas/assert/v2" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"github.com/efficientgo/e2e"
) )
func TestE2EIssue53LcAllCInfo(t *testing.T) { // Config returns a ALR configuration struct.
dockerMultipleRun( // The first time it's called, it'll load the config from a file.
t, // Subsequent calls will just return the same value.
"issue-53-lc-all-c-info", //
COMMON_SYSTEMS, // Deprecated: use struct method
func(t *testing.T, r e2e.Runnable) { func Config(ctx context.Context) *types.Config {
err := r.Exec(e2e.NewCommand( return GetInstance(ctx).cfg
"alr", }
"addrepo",
"--name", // =======================
"alr-repo", // FOR LEGACY ONLY
"--url", // =======================
"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git",
)) var (
assert.NoError(t, err) alrConfig *ALRConfig
alrConfigOnce sync.Once
err = r.Exec(e2e.NewCommand( )
"bash",
"-c", // Deprecated: For legacy only
"LANG=C alr info alr-bin", func GetInstance(ctx context.Context) *ALRConfig {
)) alrConfigOnce.Do(func() {
assert.NoError(t, err) alrConfig = New()
}, alrConfig.Load(ctx)
) })
return alrConfig
} }

70
internal/config/lang.go Normal file
View File

@@ -0,0 +1,70 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
import (
"context"
"log/slog"
"os"
"strings"
"sync"
"github.com/leonelquinteros/gotext"
"golang.org/x/text/language"
)
var (
langMtx sync.Mutex
lang language.Tag
langSet bool
)
// Language returns the system language.
// The first time it's called, it'll detect the langauge based on
// the $LANG environment variable.
// Subsequent calls will just return the same value.
func Language(ctx context.Context) language.Tag {
langMtx.Lock()
if !langSet {
syslang := SystemLang()
tag, err := language.Parse(syslang)
if err != nil {
slog.Error(gotext.Get("Error parsing system language"), "err", err)
langMtx.Unlock()
os.Exit(1)
}
base, _ := tag.Base()
lang = language.Make(base.String())
langSet = true
}
langMtx.Unlock()
return lang
}
// SystemLang returns the system language based on
// the $LANG environment variable.
func SystemLang() string {
lang := os.Getenv("LANG")
lang, _, _ = strings.Cut(lang, ".")
if lang == "" || lang == "C" {
lang = "en"
}
return lang
}

View File

@@ -19,11 +19,27 @@
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 {
UserConfigPath string ConfigDir string
CacheDir string ConfigPath string
RepoDir string CacheDir string
PkgsDir string RepoDir string
DBPath string PkgsDir string
DBPath string
}
// GetPaths returns a Paths struct.
// The first time it's called, it'll generate the struct
// using information from the system.
// Subsequent calls will return the same value.
//
// Deprecated: use struct API
func GetPaths(ctx context.Context) *Paths {
alrConfig := GetInstance(ctx)
return alrConfig.GetPaths(ctx)
} }

View File

@@ -59,7 +59,7 @@ type version struct {
} }
type Config interface { type Config interface {
GetPaths() *config.Paths GetPaths(ctx context.Context) *config.Paths
} }
type Database struct { type Database struct {
@@ -82,7 +82,7 @@ func (d *Database) Init(ctx context.Context) error {
} }
func (d *Database) Connect(ctx context.Context) error { func (d *Database) Connect(ctx context.Context) error {
dsn := d.config.GetPaths().DBPath dsn := d.config.GetPaths(ctx).DBPath
db, err := sqlx.Open("sqlite", dsn) db, err := sqlx.Open("sqlite", dsn)
if err != nil { if err != nil {
return err return err

106
internal/db/db_legacy.go Normal file
View 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
}

View File

@@ -33,7 +33,7 @@ import (
type TestALRConfig struct{} type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
DBPath: ":memory:", DBPath: ":memory:",
} }

View File

@@ -38,7 +38,7 @@ import (
type TestALRConfig struct{} type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
CacheDir: "/tmp", CacheDir: "/tmp",
} }
@@ -91,7 +91,7 @@ func TestDownloadWithoutCache(t *testing.T) {
}, },
{ {
name: "git download", name: "git download",
path: "git+%s/git-downloader/git/Plemya-x/alr-repo", path: "git+%s/git-downloader/git/Plemya-x/xpamych-alr-repo",
expected: func(t *testing.T, err error, tmpdir string) { expected: func(t *testing.T, err error, tmpdir string) {
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -28,7 +28,7 @@ import (
) )
type Config interface { type Config interface {
GetPaths() *config.Paths GetPaths(ctx context.Context) *config.Paths
} }
type DownloadCache struct { type DownloadCache struct {
@@ -43,7 +43,7 @@ func New(cfg Config) *DownloadCache {
func (dc *DownloadCache) BasePath(ctx context.Context) string { func (dc *DownloadCache) BasePath(ctx context.Context) string {
return filepath.Join( return filepath.Join(
dc.cfg.GetPaths().CacheDir, "dl", dc.cfg.GetPaths(ctx).CacheDir, "dl",
) )
} }

View File

@@ -32,11 +32,19 @@ 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
} }
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
CacheDir: c.CacheDir, CacheDir: c.CacheDir,
} }

View File

@@ -62,25 +62,6 @@ func New() *Logger {
} }
} }
func slogLevelToLog(level slog.Level) log.Level {
switch level {
case slog.LevelDebug:
return log.DebugLevel
case slog.LevelInfo:
return log.InfoLevel
case slog.LevelWarn:
return log.WarnLevel
case slog.LevelError:
return log.ErrorLevel
}
return log.FatalLevel
}
func (l *Logger) SetLevel(level slog.Level) {
l.lOut.(*log.Logger).SetLevel(slogLevelToLog(level))
l.lErr.(*log.Logger).SetLevel(slogLevelToLog(level))
}
func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool { func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool {
if level <= slog.LevelInfo { if level <= slog.LevelInfo {
return l.lOut.Enabled(ctx, level) return l.lOut.Enabled(ctx, level)
@@ -109,9 +90,7 @@ func (l *Logger) WithGroup(name string) slog.Handler {
return &sl return &sl
} }
func SetupDefault() *Logger { func SetupDefault() {
l := New() logger := slog.New(New())
logger := slog.New(l)
slog.SetDefault(logger) slog.SetDefault(logger)
return l
} }

View File

@@ -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 {

View File

@@ -164,10 +164,7 @@ 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
@@ -200,7 +197,10 @@ func (d *Decoder) GetFuncP(name string, prepare PrepareFunc) (ScriptFunc, bool)
}, true }, true
} }
func (d *Decoder) GetFuncWithSubshell(name string) (ScriptFuncWithSubshell, bool) { // TODO: replace
func (d *Decoder) GetFuncSub(name string) (
func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error), bool,
) {
fn := d.getFunc(name) fn := d.getFunc(name)
if fn == nil { if fn == nil {
return nil, false return nil, false

View File

@@ -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
} }

View File

@@ -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)
} }
} }

View File

@@ -18,7 +18,7 @@ msgid "Path to the build script"
msgstr "" msgstr ""
#: build.go:55 #: build.go:55
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify package in script (for multi package script only)"
msgstr "" msgstr ""
#: build.go:60 #: build.go:60
@@ -30,39 +30,35 @@ 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:73 #: build.go:75
msgid "Error loading config" msgid "Error db init"
msgstr "" msgstr ""
#: build.go:81 #: build.go:104
msgid "Error initialization database"
msgstr ""
#: build.go:110
msgid "Package not found" msgid "Package not found"
msgstr "" msgstr ""
#: build.go:130 #: build.go:122
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "" msgstr ""
#: build.go:138 #: build.go:130
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:144 #: build.go:136
msgid "Error parsing os release" msgid "Error parsing os release"
msgstr "" msgstr ""
#: build.go:166 #: build.go:157
msgid "Error building package" msgid "Error building package"
msgstr "" msgstr ""
#: build.go:173 #: build.go:164
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "" msgstr ""
#: build.go:182 #: build.go:173
msgid "Error moving the package" msgid "Error moving the package"
msgstr "" msgstr ""
@@ -70,27 +66,27 @@ msgstr ""
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "" msgstr ""
#: fix.go:49 #: fix.go:44
msgid "Removing cache directory" msgid "Removing cache directory"
msgstr "" msgstr ""
#: fix.go:53 #: fix.go:48
msgid "Unable to remove cache directory" msgid "Unable to remove cache directory"
msgstr "" msgstr ""
#: fix.go:57 #: fix.go:52
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "" msgstr ""
#: fix.go:61 #: fix.go:56
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "" msgstr ""
#: fix.go:81 #: fix.go:62
msgid "Error pulling repos" msgid "Error pulling repos"
msgstr "" msgstr ""
#: fix.go:85 #: fix.go:66
msgid "Done" msgid "Done"
msgstr "" msgstr ""
@@ -118,39 +114,35 @@ 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:69 #: info.go:57
msgid "Error getting packages" msgid "Error initialization database"
msgstr "" msgstr ""
#: info.go:78 #: info.go:64
msgid "Error iterating over packages"
msgstr ""
#: info.go:105
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: info.go:119 #: info.go:78
msgid "Error finding packages" msgid "Error finding packages"
msgstr "" msgstr ""
#: info.go:144 #: info.go:94
msgid "Error parsing os-release file" msgid "Error parsing os-release file"
msgstr "" msgstr ""
#: info.go:153 #: info.go:103
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "" msgstr ""
#: info.go:162 info.go:168 #: info.go:112 info.go:118
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "" msgstr ""
@@ -162,15 +154,23 @@ msgstr ""
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:157 #: install.go:124
msgid "Error getting packages"
msgstr ""
#: install.go:133
msgid "Error iterating over packages"
msgstr ""
#: install.go:146
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "" msgstr ""
#: install.go:162 #: install.go:151
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:177 #: install.go:163
msgid "Error removing packages" msgid "Error removing packages"
msgstr "" msgstr ""
@@ -198,74 +198,46 @@ msgstr ""
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:93 #: internal/config/config.go:64
msgid "NAME" msgid "Error opening config file, using defaults"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:94 #: internal/config/config.go:77
msgid "USAGE" msgid "Error decoding config file, using defaults"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/config/config.go:89
msgid "global options" msgid "Unable to detect user config directory"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/config/config.go:97
msgid "command" msgid "Unable to create ALR config directory"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:95 #: internal/config/config.go:106
msgid "command options" msgid "Unable to create ALR config file"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:96 #: internal/config/config.go:112
msgid "arguments" msgid "Error encoding default configuration"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/config/config.go:121
msgid "VERSION" msgid "Unable to detect cache directory"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:98 #: internal/config/config.go:131
msgid "DESCRIPTION"
msgstr ""
#: internal/cliutils/template.go:74
msgid "AUTHOR"
msgstr ""
#: internal/cliutils/template.go:74
msgid "COMMANDS"
msgstr ""
#: internal/cliutils/template.go:74
msgid "GLOBAL OPTIONS"
msgstr ""
#: internal/cliutils/template.go:74
msgid "COPYRIGHT"
msgstr ""
#: internal/cliutils/template.go:97
msgid "CATEGORY"
msgstr ""
#: internal/cliutils/template.go:99 internal/cliutils/template.go:100
msgid "OPTIONS"
msgstr ""
#: internal/config/config.go:176
msgid "Unable to create config directory"
msgstr ""
#: internal/config/config.go:182
msgid "Unable to create repo cache directory" msgid "Unable to create repo cache directory"
msgstr "" msgstr ""
#: internal/config/config.go:188 #: internal/config/config.go:137
msgid "Unable to create package cache directory" msgid "Unable to create package cache directory"
msgstr "" msgstr ""
#: internal/config/lang.go:49
msgid "Error parsing system language"
msgstr ""
#: internal/db/db.go:133 #: internal/db/db.go:133
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "" msgstr ""
@@ -275,6 +247,10 @@ 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 ""
@@ -303,11 +279,11 @@ msgstr ""
msgid "ERROR" msgid "ERROR"
msgstr "" msgstr ""
#: list.go:41 #: list.go:40
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "" msgstr ""
#: list.go:98 #: list.go:91
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "" msgstr ""
@@ -323,94 +299,90 @@ msgstr ""
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "" msgstr ""
#: main.go:96 #: 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:154 #: main.go:124
msgid "Show help"
msgstr ""
#: main.go:158
msgid "Error while running app" msgid "Error while running app"
msgstr "" msgstr ""
#: pkg/build/build.go:156 #: pkg/build/build.go:135
msgid "Failed to prompt user to view build script" msgid "Failed to prompt user to view build script"
msgstr "" msgstr ""
#: pkg/build/build.go:160 #: pkg/build/build.go:139
msgid "Building package" msgid "Building package"
msgstr "" msgstr ""
#: pkg/build/build.go:208 #: pkg/build/build.go:183
msgid "The checksums array must be the same length as sources"
msgstr ""
#: pkg/build/build.go:235
msgid "Downloading sources" msgid "Downloading sources"
msgstr "" msgstr ""
#: pkg/build/build.go:257 #: pkg/build/build.go:195
msgid "Building package metadata" msgid "Building package metadata"
msgstr "" msgstr ""
#: pkg/build/build.go:279 #: pkg/build/build.go:217
msgid "Compressing package" msgid "Compressing package"
msgstr "" msgstr ""
#: pkg/build/build.go:438 #: pkg/build/build.go:359
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:452 #: pkg/build/build.go:373
msgid "This package is already installed" msgid "This package is already installed"
msgstr "" msgstr ""
#: pkg/build/build.go:476 #: pkg/build/build.go:397
msgid "Installing build dependencies" msgid "Installing build dependencies"
msgstr "" msgstr ""
#: pkg/build/build.go:521 #: pkg/build/build.go:408
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "" msgstr ""
#: pkg/build/build.go:602 #: pkg/build/build.go:449
msgid "The checksums array must be the same length as sources"
msgstr ""
#: pkg/build/build.go:500
msgid "Would you like to remove the build dependencies?" msgid "Would you like to remove the build dependencies?"
msgstr "" msgstr ""
#: pkg/build/build.go:665 #: pkg/build/build.go:537
msgid "Executing version()"
msgstr ""
#: pkg/build/build.go:557
msgid "Updating version"
msgstr ""
#: pkg/build/build.go:562
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "" msgstr ""
#: pkg/build/build.go:675 #: pkg/build/build.go:572
msgid "Executing build()" msgid "Executing build()"
msgstr "" msgstr ""
#: pkg/build/build.go:705 pkg/build/build.go:725 #: pkg/build/build.go:588 pkg/build/build.go:616
msgid "Executing %s()" msgid "Executing %s()"
msgstr "" msgstr ""
#: pkg/build/build.go:784 #: pkg/build/build.go:675
msgid "Error installing native packages" msgid "Error installing native packages"
msgstr "" msgstr ""
#: pkg/build/build.go:808 #: pkg/build/build.go:701
msgid "Error installing package" msgid "Error installing package"
msgstr "" msgstr ""
#: pkg/build/build.go:867
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/build.go:878
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/findDeps.go:35 #: pkg/build/findDeps.go:35
msgid "Command not found on the system" msgid "Command not found on the system"
msgstr "" msgstr ""
@@ -423,6 +395,14 @@ msgstr ""
msgid "Required dependency found" msgid "Required dependency found"
msgstr "" msgstr ""
#: pkg/build/utils.go:136
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/utils.go:147
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/repos/pull.go:79 #: pkg/repos/pull.go:79
msgid "Pulling repository" msgid "Pulling repository"
msgstr "" msgstr ""
@@ -441,86 +421,58 @@ msgid ""
"updating ALR if something doesn't work." "updating ALR if something doesn't work."
msgstr "" msgstr ""
#: repo.go:40 #: repo.go:41
msgid "Add a new repository" msgid "Add a new repository"
msgstr "" msgstr ""
#: repo.go:47 #: repo.go:48
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "" msgstr ""
#: repo.go:53 #: repo.go:54
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "" msgstr ""
#: repo.go:86 repo.go:156 #: repo.go:79 repo.go:136
msgid "Error saving config" msgid "Error opening config file"
msgstr "" msgstr ""
#: repo.go:111 #: repo.go:85 repo.go:142
msgid "Error encoding config"
msgstr ""
#: repo.go:103
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "" msgstr ""
#: repo.go:118 #: repo.go:110
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "" msgstr ""
#: repo.go:142 #: repo.go:128
msgid "Repo does not exist" msgid "Repo does not exist"
msgstr "" msgstr ""
#: repo.go:150 #: repo.go:148
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "" msgstr ""
#: repo.go:167 #: repo.go:154
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "" msgstr ""
#: repo.go:179 #: 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:88 search.go:105
msgid "Error parsing format template"
msgstr ""
#: search.go:113
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:96 #: upgrade.go:90
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "" msgstr ""
#: upgrade.go:118 #: upgrade.go:112
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "" msgstr ""

View File

@@ -1,12 +1,12 @@
# #
# x1z53 <x1z53@yandex.ru>, 2025.
# Maxim Slipenko <maks1ms@alt-gnome.ru>, 2025. # Maxim Slipenko <maks1ms@alt-gnome.ru>, 2025.
# x1z53 <x1z53@yandex.ru>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: unnamed project\n" "Project-Id-Version: unnamed project\n"
"PO-Revision-Date: 2025-03-09 17:31+0300\n" "PO-Revision-Date: 2025-01-24 21:20+0300\n"
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" "Last-Translator: x1z53 <x1z53@yandex.ru>\n"
"Language-Team: Russian\n" "Language-Team: Russian\n"
"Language: ru\n" "Language: ru\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@@ -25,8 +25,8 @@ msgid "Path to the build script"
msgstr "Путь к скрипту сборки" msgstr "Путь к скрипту сборки"
#: build.go:55 #: build.go:55
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify package in script (for multi package script only)"
msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" msgstr ""
#: build.go:60 #: 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)"
@@ -37,40 +37,36 @@ 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:73 #: build.go:75
#, fuzzy msgid "Error db init"
msgid "Error loading config" msgstr ""
msgstr "Ошибка при кодировании конфигурации"
#: build.go:81 #: build.go:104
msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных"
#: build.go:110
msgid "Package not found" msgid "Package not found"
msgstr "Пакет не найден" msgstr ""
#: build.go:130 #: build.go:122
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев" msgstr "Ошибка при извлечении репозиториев"
#: build.go:138 #: build.go:130
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:144 #: build.go:136
#, fuzzy
msgid "Error parsing os release" msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы" msgstr "Ошибка при разборе файла выпуска операционной системы"
#: build.go:166 #: build.go:157
msgid "Error building package" msgid "Error building package"
msgstr "Ошибка при сборке пакета" msgstr "Ошибка при сборке пакета"
#: build.go:173 #: build.go:164
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога" msgstr "Ошибка при получении рабочего каталога"
#: build.go:182 #: build.go:173
msgid "Error moving the package" msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета" msgstr "Ошибка при перемещении пакета"
@@ -78,27 +74,27 @@ msgstr "Ошибка при перемещении пакета"
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR" msgstr "Попытка устранить проблемы с ALR"
#: fix.go:49 #: fix.go:44
msgid "Removing cache directory" msgid "Removing cache directory"
msgstr "Удаление каталога кэша" msgstr "Удаление каталога кэша"
#: fix.go:53 #: fix.go:48
msgid "Unable to remove cache directory" msgid "Unable to remove cache directory"
msgstr "Не удалось удалить каталог кэша" msgstr "Не удалось удалить каталог кэша"
#: fix.go:57 #: fix.go:52
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "Восстановление кэша" msgstr "Восстановление кэша"
#: fix.go:61 #: fix.go:56
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша" msgstr "Не удалось создать новый каталог кэша"
#: fix.go:81 #: fix.go:62
msgid "Error pulling repos" msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев" msgstr "Ошибка при извлечении репозиториев"
#: fix.go:85 #: fix.go:66
msgid "Done" msgid "Done"
msgstr "Сделано" msgstr "Сделано"
@@ -126,39 +122,35 @@ 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:69 #: info.go:57
msgid "Error getting packages" msgid "Error initialization database"
msgstr "Ошибка при получении пакетов" msgstr "Ошибка инициализации базы данных"
#: info.go:78 #: info.go:64
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
#: info.go:105
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:119 #: info.go:78
msgid "Error finding packages" msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов" msgstr "Ошибка при поиске пакетов"
#: info.go:144 #: info.go:94
msgid "Error parsing os-release file" msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы" msgstr "Ошибка при разборе файла выпуска операционной системы"
#: info.go:153 #: info.go:103
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений" msgstr "Ошибка устранения переорпеделений"
#: info.go:162 info.go:168 #: info.go:112 info.go:118
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита" msgstr "Ошибка кодирования переменных скрита"
@@ -170,15 +162,23 @@ msgstr "Установить новый пакет"
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:157 #: install.go:124
msgid "Error getting packages"
msgstr "Ошибка при получении пакетов"
#: install.go:133
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
#: install.go:146
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "Удалить установленный пакет" msgstr "Удалить установленный пакет"
#: install.go:162 #: install.go:151
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:177 #: install.go:163
msgid "Error removing packages" msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов" msgstr "Ошибка при удалении пакетов"
@@ -206,75 +206,50 @@ msgstr "Выберите, какой пакет использовать для
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "Выберите, какой дополнительный пакет(ы) следует установить" msgstr "Выберите, какой дополнительный пакет(ы) следует установить"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:93 #: internal/config/config.go:64
msgid "NAME" msgid "Error opening config file, using defaults"
msgstr "НАЗВАНИЕ" msgstr ""
"Ошибка при открытии конфигурационного файла, используются значения по "
"умолчанию"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:94 #: internal/config/config.go:77
msgid "USAGE" msgid "Error decoding config file, using defaults"
msgstr "ИСПОЛЬЗОВАНИЕ" msgstr ""
"Ошибка при декодировании конфигурационного файла, используются значения по "
"умолчанию"
#: internal/cliutils/template.go:74 #: internal/config/config.go:89
msgid "global options" msgid "Unable to detect user config directory"
msgstr "глобальные опции" msgstr "Не удалось обнаружить каталог конфигурации пользователя"
#: internal/cliutils/template.go:74 #: internal/config/config.go:97
msgid "command" msgid "Unable to create ALR config directory"
msgstr "команда"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:95
msgid "command options"
msgstr "опции команды"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:96
msgid "arguments"
msgstr "аргументы"
#: internal/cliutils/template.go:74
msgid "VERSION"
msgstr "ВЕРСИЯ"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:98
msgid "DESCRIPTION"
msgstr "ОПИСАНИЕ"
#: internal/cliutils/template.go:74
msgid "AUTHOR"
msgstr "АВТОР"
#: internal/cliutils/template.go:74
msgid "COMMANDS"
msgstr "КОМАНДЫ"
#: internal/cliutils/template.go:74
msgid "GLOBAL OPTIONS"
msgstr "ГЛОБАЛЬНЫЕ ОПЦИИ"
#: internal/cliutils/template.go:74
msgid "COPYRIGHT"
msgstr "АВТОРСКОЕ ПРАВО"
#: internal/cliutils/template.go:97
msgid "CATEGORY"
msgstr "КАТЕГОРИЯ"
#: internal/cliutils/template.go:99 internal/cliutils/template.go:100
msgid "OPTIONS"
msgstr "ПАРАМЕТРЫ"
#: internal/config/config.go:176
#, fuzzy
msgid "Unable to create config directory"
msgstr "Не удалось создать каталог конфигурации ALR" msgstr "Не удалось создать каталог конфигурации ALR"
#: internal/config/config.go:182 #: internal/config/config.go:106
msgid "Unable to create ALR config file"
msgstr "Не удалось создать конфигурационный файл ALR"
#: internal/config/config.go:112
msgid "Error encoding default configuration"
msgstr "Ошибка кодирования конфигурации по умолчанию"
#: internal/config/config.go:121
msgid "Unable to detect cache directory"
msgstr "Не удалось обнаружить каталог кэша"
#: 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:188 #: internal/config/config.go:137
msgid "Unable to create package cache directory" msgid "Unable to create package cache directory"
msgstr "Не удалось создать каталог кэша пакетов" msgstr "Не удалось создать каталог кэша пакетов"
#: internal/config/lang.go:49
msgid "Error parsing system language"
msgstr "Ошибка при парсинге языка системы"
#: internal/db/db.go:133 #: internal/db/db.go:133
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек" msgstr "Несоответствие версий базы данных; сброс настроек"
@@ -285,6 +260,10 @@ msgid ""
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 "Исходный код можно обновлять, обновляя при необходимости"
@@ -303,21 +282,21 @@ msgstr "Скачивание источника"
#: internal/dl/progress_tui.go:100 #: internal/dl/progress_tui.go:100
msgid "%s: done!\n" msgid "%s: done!\n"
msgstr "%s: выполнено!\n" msgstr ""
#: internal/dl/progress_tui.go:104 #: internal/dl/progress_tui.go:104
msgid "%s %s downloading at %s/s\n" msgid "%s %s downloading at %s/s\n"
msgstr "%s %s загружается — %s/с\n" msgstr ""
#: internal/logger/log.go:47 #: internal/logger/log.go:47
msgid "ERROR" msgid "ERROR"
msgstr "ОШИБКА" msgstr "ОШИБКА"
#: list.go:41 #: list.go:40
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "Список пакетов репозитория ALR" msgstr "Список пакетов репозитория ALR"
#: list.go:98 #: list.go:91
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов" msgstr "Ошибка при составлении списка установленных пакетов"
@@ -333,7 +312,7 @@ msgstr "Аргументы, которые будут переданы мене
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов" msgstr "Включение интерактивных вопросов и запросов"
#: main.go:96 #: 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"
@@ -341,39 +320,31 @@ msgstr ""
"Запуск ALR от имени root запрещён, так как это может привести к " "Запуск ALR от имени root запрещён, так как это может привести к "
"катастрофическому повреждению вашей системы" "катастрофическому повреждению вашей системы"
#: main.go:154 #: main.go:124
msgid "Show help"
msgstr "Показать справку"
#: main.go:158
msgid "Error while running app" msgid "Error while running app"
msgstr "Ошибка при запуске приложения" msgstr "Ошибка при запуске приложения"
#: pkg/build/build.go:156 #: pkg/build/build.go:135
msgid "Failed to prompt user to view build script" msgid "Failed to prompt user to view build script"
msgstr "Не удалось предложить пользователю просмотреть скрипт сборки" msgstr "Не удалось предложить пользователю просмотреть скрипт сборки"
#: pkg/build/build.go:160 #: pkg/build/build.go:139
msgid "Building package" msgid "Building package"
msgstr "Сборка пакета" msgstr "Сборка пакета"
#: pkg/build/build.go:208 #: pkg/build/build.go:183
msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: pkg/build/build.go:235
msgid "Downloading sources" msgid "Downloading sources"
msgstr "Скачивание источников" msgstr "Скачивание источников"
#: pkg/build/build.go:257 #: pkg/build/build.go:195
msgid "Building package metadata" msgid "Building package metadata"
msgstr "Сборка метаданных пакета" msgstr "Сборка метаданных пакета"
#: pkg/build/build.go:279 #: pkg/build/build.go:217
msgid "Compressing package" msgid "Compressing package"
msgstr "Сжатие пакета" msgstr "Сжатие пакета"
#: pkg/build/build.go:438 #: pkg/build/build.go:359
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?"
@@ -381,52 +352,55 @@ msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все " "Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?" "равно хотите выполнить сборку?"
#: pkg/build/build.go:452 #: pkg/build/build.go:373
msgid "This package is already installed" msgid "This package is already installed"
msgstr "Этот пакет уже установлен" msgstr "Этот пакет уже установлен"
#: pkg/build/build.go:476 #: pkg/build/build.go:397
msgid "Installing build dependencies" msgid "Installing build dependencies"
msgstr "Установка зависимостей сборки" msgstr "Установка зависимостей сборки"
#: pkg/build/build.go:521 #: pkg/build/build.go:408
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "Установка зависимостей" msgstr "Установка зависимостей"
#: pkg/build/build.go:602 #: pkg/build/build.go:449
msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: pkg/build/build.go:500
msgid "Would you like to remove the build dependencies?" msgid "Would you like to remove the build dependencies?"
msgstr "Хотели бы вы удалить зависимости сборки?" msgstr "Хотели бы вы удалить зависимости сборки?"
#: pkg/build/build.go:665 #: pkg/build/build.go:537
msgid "Executing version()"
msgstr "Исполнение версия()"
#: pkg/build/build.go:557
msgid "Updating version"
msgstr "Обновление версии"
#: pkg/build/build.go:562
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "Исполнение prepare()" msgstr "Исполнение prepare()"
#: pkg/build/build.go:675 #: pkg/build/build.go:572
msgid "Executing build()" msgid "Executing build()"
msgstr "Исполнение build()" msgstr "Исполнение build()"
#: pkg/build/build.go:705 pkg/build/build.go:725 #: pkg/build/build.go:588 pkg/build/build.go:616
#, fuzzy
msgid "Executing %s()" msgid "Executing %s()"
msgstr "Исполнение %s()" msgstr "Исполнение files()"
#: pkg/build/build.go:784 #: pkg/build/build.go:675
msgid "Error installing native packages" msgid "Error installing native packages"
msgstr "Ошибка при установке нативных пакетов" msgstr "Ошибка при установке нативных пакетов"
#: pkg/build/build.go:808 #: pkg/build/build.go:701
msgid "Error installing package" msgid "Error installing package"
msgstr "Ошибка при установке пакета" msgstr "Ошибка при установке пакета"
#: pkg/build/build.go:867
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/build.go:878
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/findDeps.go:35 #: pkg/build/findDeps.go:35
msgid "Command not found on the system" msgid "Command not found on the system"
msgstr "Команда не найдена в системе" msgstr "Команда не найдена в системе"
@@ -439,6 +413,16 @@ msgstr "Найденная предоставленная зависимость
msgid "Required dependency found" msgid "Required dependency found"
msgstr "Найдена требуемая зависимость" msgstr "Найдена требуемая зависимость"
#: pkg/build/utils.go:136
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/utils.go:147
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/repos/pull.go:79 #: pkg/repos/pull.go:79
msgid "Pulling repository" msgid "Pulling repository"
msgstr "Скачивание репозитория" msgstr "Скачивание репозитория"
@@ -459,127 +443,61 @@ msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает." "обновить ALR, если что-то не работает."
#: repo.go:40 #: repo.go:41
msgid "Add a new repository" msgid "Add a new repository"
msgstr "Добавить новый репозиторий" msgstr "Добавить новый репозиторий"
#: repo.go:47 #: repo.go:48
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "Название нового репозитория" msgstr "Название нового репозитория"
#: repo.go:53 #: repo.go:54
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория" msgstr "URL-адрес нового репозитория"
#: repo.go:86 repo.go:156 #: repo.go:79 repo.go:136
#, fuzzy msgid "Error opening config file"
msgid "Error saving config" msgstr "Ошибка при открытии конфигурационного файла"
#: repo.go:85 repo.go:142
msgid "Error encoding config"
msgstr "Ошибка при кодировании конфигурации" msgstr "Ошибка при кодировании конфигурации"
#: repo.go:111 #: repo.go:103
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий" msgstr "Удалить существующий репозиторий"
#: repo.go:118 #: repo.go:110
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён" msgstr "Название репозитория удалён"
#: repo.go:142 #: repo.go:128
msgid "Repo does not exist" msgid "Repo does not exist"
msgstr "Репозитория не существует" msgstr "Репозитория не существует"
#: repo.go:150 #: repo.go:148
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория" msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:167 #: repo.go:154
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных" msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:179 #: 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 "Иcкать по provides"
#: search.go:62
msgid "Format output using a Go template"
msgstr "Формат выходных данных с использованием шаблона Go"
#: search.go:88 search.go:105
msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона"
#: search.go:113
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:96 #: upgrade.go:90
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr "Ошибка при проверке обновлений"
#: upgrade.go:118 #: upgrade.go:112
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "Здесь нечего делать." msgstr "Здесь нечего делать."
#~ msgid "Error opening config file, using defaults"
#~ msgstr ""
#~ "Ошибка при открытии конфигурационного файла, используются значения по "
#~ "умолчанию"
#~ msgid "Error decoding config file, using defaults"
#~ msgstr ""
#~ "Ошибка при декодировании конфигурационного файла, используются значения "
#~ "по умолчанию"
#~ msgid "Unable to detect user config directory"
#~ msgstr "Не удалось обнаружить каталог конфигурации пользователя"
#~ msgid "Unable to create ALR config file"
#~ msgstr "Не удалось создать конфигурационный файл ALR"
#~ msgid "Error encoding default configuration"
#~ msgstr "Ошибка кодирования конфигурации по умолчанию"
#~ msgid "Unable to detect cache directory"
#~ msgstr "Не удалось обнаружить каталог кэша"
#~ msgid "Error opening config file"
#~ msgstr "Ошибка при открытии конфигурационного файла"
#~ msgid "Error parsing system language"
#~ msgstr "Ошибка при парсинге языка системы"
#~ msgid "Error opening database"
#~ msgstr "Ошибка при открытии базы данных"
#~ msgid "Executing version()"
#~ msgstr "Исполнение версия()"
#~ msgid "Updating version"
#~ msgstr "Обновление версии"
#~ msgid "Executing package()" #~ msgid "Executing package()"
#~ msgstr "Исполнение package()" #~ msgstr "Исполнение package()"

View File

@@ -23,8 +23,7 @@ import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
type BuildOpts struct { type BuildOpts struct {
Script string Script string
Repository string Package string
Packages []string
Manager manager.Manager Manager manager.Manager
Clean bool Clean bool
Interactive bool Interactive bool
@@ -103,7 +102,6 @@ type BuildVars struct {
Scripts Scripts `sh:"scripts"` Scripts Scripts `sh:"scripts"`
AutoReq []string `sh:"auto_req"` AutoReq []string `sh:"auto_req"`
AutoProv []string `sh:"auto_prov"` AutoProv []string `sh:"auto_prov"`
Base string
} }
type Scripts struct { type Scripts struct {

View File

@@ -21,13 +21,12 @@ package types
// Config represents the ALR configuration file // Config represents the ALR configuration file
type Config struct { type Config struct {
RootCmd string `toml:"rootCmd" env:"ALR_ROOT_CMD"` RootCmd string `toml:"rootCmd"`
PagerStyle string `toml:"pagerStyle" env:"ALR_PAGER_STYLE"` PagerStyle string `toml:"pagerStyle"`
IgnorePkgUpdates []string `toml:"ignorePkgUpdates"` IgnorePkgUpdates []string `toml:"ignorePkgUpdates"`
Repos []Repo `toml:"repo"` Repos []Repo `toml:"repo"`
Unsafe Unsafe `toml:"unsafe"` Unsafe Unsafe `toml:"unsafe"`
AutoPull bool `toml:"autoPull" env:"ALR_AUTOPULL"` AutoPull bool `toml:"autoPull"`
LogLevel string `toml:"logLevel" env:"ALR_LOG_LEVEL"`
} }
// Repo represents a ALR repo within a configuration file // Repo represents a ALR repo within a configuration file
@@ -37,5 +36,5 @@ type Repo struct {
} }
type Unsafe struct { type Unsafe struct {
AllowRunAsRoot bool `toml:"allowRunAsRoot" env:"ALR_UNSAFE_ALLOW_RUN_AS_ROOT"` AllowRunAsRoot bool `toml:"allowRunAsRoot"`
} }

30
list.go
View File

@@ -30,7 +30,6 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
@@ -49,22 +48,16 @@ func ListCmd() *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.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg) db := database.New(cfg)
err = db.Init(ctx) err := db.Init(ctx)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err) slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1) os.Exit(1)
} }
rs := repos.New(cfg, db) rs := repos.New(cfg, db)
if cfg.AutoPull() { if cfg.AutoPull(ctx) {
err = rs.Pull(ctx, cfg.Repos()) err = rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err) slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1) os.Exit(1)
@@ -85,7 +78,7 @@ func ListCmd() *cli.Command {
} }
defer result.Close() defer result.Close()
installedAlrPackages := map[string]string{} var installed map[string]string
if c.Bool("installed") { if c.Bool("installed") {
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
@@ -93,20 +86,11 @@ func ListCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
installed, err := mgr.ListInstalled(&manager.Opts{AsRoot: false}) installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false})
if err != nil { if err != nil {
slog.Error(gotext.Get("Error listing installed packages"), "err", err) slog.Error(gotext.Get("Error listing installed packages"), "err", err)
os.Exit(1) os.Exit(1)
} }
for pkgName, version := range installed {
matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
if matches != nil {
packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")]
repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")]
installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version
}
}
} }
for result.Next() { for result.Next() {
@@ -116,13 +100,13 @@ func ListCmd() *cli.Command {
return err return err
} }
if slices.Contains(cfg.IgnorePkgUpdates(), pkg.Name) { if slices.Contains(cfg.IgnorePkgUpdates(ctx), pkg.Name) {
continue continue
} }
version := pkg.Version version := pkg.Version
if c.Bool("installed") { if c.Bool("installed") {
instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] instVersion, ok := installed[pkg.Name]
if !ok { if !ok {
continue continue
} else { } else {

56
main.go
View File

@@ -31,8 +31,8 @@ import (
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/translations" "gitea.plemya-x.ru/Plemya-x/ALR/internal/translations"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
@@ -81,18 +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 {
cfg := config.New() ctx := c.Context
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
cmd := c.Args().First() cmd := c.Args().First()
if cmd != "helper" && !cfg.AllowRunAsRoot() && 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)
} }
@@ -104,56 +98,28 @@ func GetApp() *cli.App {
return nil return nil
}, },
After: func(ctx *cli.Context) error {
return db.Close()
},
EnableBashCompletion: true, EnableBashCompletion: true,
} }
} }
func setLogLevel(newLevel string) {
level := slog.LevelInfo
switch newLevel {
case "DEBUG":
level = slog.LevelDebug
case "INFO":
level = slog.LevelInfo
case "WARN":
level = slog.LevelWarn
case "ERROR":
level = slog.LevelError
}
logger, ok := slog.Default().Handler().(*logger.Logger)
if !ok {
panic("unexpected")
}
logger.SetLevel(level)
}
func main() { func main() {
logger.SetupDefault()
setLogLevel(os.Getenv("ALR_LOG_LEVEL"))
translations.Setup() translations.Setup()
logger.SetupDefault()
app := GetApp()
ctx := context.Background() ctx := context.Background()
app := GetApp()
cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
setLogLevel(cfg.LogLevel())
// 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() 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()
// Make the application more internationalized err := app.RunContext(ctx, os.Args)
cli.AppHelpTemplate = cliutils.GetAppCliTemplate()
cli.CommandHelpTemplate = cliutils.GetCommandHelpTemplate()
cli.HelpFlag.(*cli.BoolFlag).Usage = gotext.Get("Show help")
err = app.RunContext(ctx, os.Args)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error while running app"), "err", err) slog.Error(gotext.Get("Error while running app"), "err", err)
} }

View File

@@ -28,8 +28,6 @@ import (
"log/slog" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"strconv"
"strings" "strings"
"time" "time"
@@ -59,8 +57,8 @@ type PackageFinder interface {
} }
type Config interface { type Config interface {
GetPaths() *config.Paths GetPaths(ctx context.Context) *config.Paths
PagerStyle() string PagerStyle(ctx context.Context) string
} }
type Builder struct { type Builder struct {
@@ -87,12 +85,11 @@ func NewBuilder(
} }
} }
func (b *Builder) UpdateOptsFromPkg(pkg *db.Package, packages []string) { func (b *Builder) UpdateOptsFromPkg(pkg *db.Package) {
repodir := b.config.GetPaths().RepoDir repodir := b.config.GetPaths(b.ctx).RepoDir
b.opts.Repository = pkg.Repository
if pkg.BasePkgName != "" { if pkg.BasePkgName != "" {
b.opts.Script = filepath.Join(repodir, pkg.Repository, pkg.BasePkgName, "alr.sh") b.opts.Script = filepath.Join(repodir, pkg.Repository, pkg.BasePkgName, "alr.sh")
b.opts.Packages = packages b.opts.Package = pkg.Name
} else { } else {
b.opts.Script = filepath.Join(repodir, pkg.Repository, pkg.Name, "alr.sh") b.opts.Script = filepath.Join(repodir, pkg.Repository, pkg.Name, "alr.sh")
} }
@@ -106,41 +103,23 @@ func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error)
// Первый проход предназначен для получения значений переменных и выполняется // Первый проход предназначен для получения значений переменных и выполняется
// до отображения скрипта, чтобы предотвратить выполнение вредоносного кода. // до отображения скрипта, чтобы предотвратить выполнение вредоносного кода.
basePkg, varsOfPackages, err := b.executeFirstPass(fl) vars, err := b.executeFirstPass(fl)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
dirs, err := b.getDirs(basePkg) dirs := b.getDirs(vars)
if err != nil {
return nil, nil, err
}
builtPaths := make([]string, 0)
// Если флаг opts.Clean не установлен, и пакет уже собран, // Если флаг opts.Clean не установлен, и пакет уже собран,
// возвращаем его, а не собираем заново. // возвращаем его, а не собираем заново.
if !b.opts.Clean { if !b.opts.Clean {
var remainingVars []*types.BuildVars builtPkgPath, ok, err := checkForBuiltPackage(b.opts.Manager, vars, getPkgFormat(b.opts.Manager), dirs.BaseDir)
for _, vars := range varsOfPackages { if err != nil {
builtPkgPath, ok, err := b.checkForBuiltPackage( return nil, nil, err
vars,
getPkgFormat(b.opts.Manager),
dirs.BaseDir,
)
if err != nil {
return nil, nil, err
}
if ok {
builtPaths = append(builtPaths, builtPkgPath)
} else {
remainingVars = append(remainingVars, vars)
}
} }
if len(remainingVars) == 0 { if ok {
return builtPaths, nil, nil return []string{builtPkgPath}, nil, err
} }
} }
@@ -148,8 +127,8 @@ func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error)
err = cliutils.PromptViewScript( err = cliutils.PromptViewScript(
ctx, ctx,
b.opts.Script, b.opts.Script,
basePkg, vars.Name,
b.config.PagerStyle(), b.config.PagerStyle(ctx),
b.opts.Interactive, b.opts.Interactive,
) )
if err != nil { if err != nil {
@@ -157,7 +136,7 @@ func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error)
os.Exit(1) os.Exit(1)
} }
slog.Info(gotext.Get("Building package"), "name", basePkg) slog.Info(gotext.Get("Building package"), "name", vars.Name, "version", vars.Version)
// Второй проход будет использоваться для выполнения реального кода, // Второй проход будет использоваться для выполнения реального кода,
// поэтому он не ограничен. Скрипт уже был показан // поэтому он не ограничен. Скрипт уже был показан
@@ -173,13 +152,11 @@ func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error)
return nil, nil, err return nil, nil, err
} }
for _, vars := range varsOfPackages { cont, err := b.performChecks(ctx, vars, installed) // Выполняем различные проверки
cont, err := b.performChecks(ctx, vars, installed) // Выполняем различные проверки if err != nil {
if err != nil { return nil, nil, err
return nil, nil, err } else if !cont {
} else if !cont { os.Exit(1) // Если проверки не пройдены, выходим из программы
os.Exit(1) // Если проверки не пройдены, выходим из программы
}
} }
// Подготавливаем директории для сборки // Подготавливаем директории для сборки
@@ -188,105 +165,60 @@ func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error)
return nil, nil, err return nil, nil, err
} }
buildDepends := []string{} buildDeps, err := b.installBuildDeps(ctx, vars) // Устанавливаем зависимости для сборки
optDepends := []string{}
depends := []string{}
sources := []string{}
checksums := []string{}
for _, vars := range varsOfPackages {
buildDepends = append(buildDepends, vars.BuildDepends...)
optDepends = append(optDepends, vars.OptDepends...)
depends = append(depends, vars.Depends...)
sources = append(sources, vars.Sources...)
checksums = append(checksums, vars.Checksums...)
}
buildDepends = removeDuplicates(buildDepends)
optDepends = removeDuplicates(optDepends)
depends = removeDuplicates(depends)
if len(sources) != len(checksums) {
slog.Error(gotext.Get("The checksums array must be the same length as sources"))
os.Exit(1)
}
sources, checksums = removeDuplicatesSources(sources, checksums)
mergedVars := types.BuildVars{
Sources: sources,
Checksums: checksums,
}
buildDeps, err := b.installBuildDeps(ctx, buildDepends) // Устанавливаем зависимости для сборки
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = b.installOptDeps(ctx, optDepends) // Устанавливаем опциональные зависимости err = b.installOptDeps(ctx, vars) // Устанавливаем опциональные зависимости
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
newBuildPaths, builtNames, repoDeps, err := b.buildALRDeps(ctx, depends) // Собираем зависимости builtPaths, builtNames, repoDeps, err := b.buildALRDeps(ctx, vars) // Собираем зависимости
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
builtPaths = append(builtPaths, newBuildPaths...)
slog.Info(gotext.Get("Downloading sources")) // Записываем в лог загрузку источников slog.Info(gotext.Get("Downloading sources")) // Записываем в лог загрузку источников
err = b.getSources(ctx, dirs, &mergedVars) // Загружаем исходники err = b.getSources(ctx, dirs, vars) // Загружаем исходники
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = b.executeFunctions(ctx, dec, dirs) // Выполняем специальные функции funcOut, err := b.executeFunctions(ctx, dec, dirs, vars) // Выполняем специальные функции
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
for _, vars := range varsOfPackages { slog.Info(gotext.Get("Building package metadata"), "name", vars.Name)
packageName := ""
if vars.Base != "" {
packageName = vars.Name
}
funcOut, err := b.executePackageFunctions(ctx, dec, dirs, packageName)
if err != nil {
return nil, nil, err
}
slog.Info(gotext.Get("Building package metadata"), "name", basePkg) pkgFormat := getPkgFormat(b.opts.Manager) // Получаем формат пакета
pkgFormat := getPkgFormat(b.opts.Manager) // Получаем формат пакета pkgInfo, err := buildPkgMetadata(ctx, vars, dirs, pkgFormat, b.info, append(repoDeps, builtNames...), funcOut.Contents) // Собираем метаданные пакета
if err != nil {
return nil, nil, err
}
pkgInfo, err := b.buildPkgMetadata(ctx, vars, dirs, pkgFormat, append(repoDeps, builtNames...), funcOut.Contents) // Собираем метаданные пакета packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
if err != nil { pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
return nil, nil, err
}
pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета pkgFile, err := os.Create(pkgPath) // Создаём файл пакета
pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету if err != nil {
return nil, nil, err
}
pkgFile, err := os.Create(pkgPath) // Создаём файл пакета slog.Info(gotext.Get("Compressing package"), "name", pkgName) // Логгируем сжатие пакета
if err != nil {
return nil, nil, err
}
slog.Info(gotext.Get("Compressing package"), "name", pkgName) // Логгируем сжатие пакета err = packager.Package(pkgInfo, pkgFile) // Упаковываем пакет
if err != nil {
err = packager.Package(pkgInfo, pkgFile) // Упаковываем пакет return nil, nil, err
if err != nil {
return nil, nil, err
}
// Добавляем путь и имя только что собранного пакета в
// соответствующие срезы
builtPaths = append(builtPaths, pkgPath)
builtNames = append(builtNames, vars.Name)
} }
err = b.removeBuildDeps(ctx, buildDeps) // Удаляем зависимости для сборки err = b.removeBuildDeps(ctx, buildDeps) // Удаляем зависимости для сборки
@@ -294,6 +226,11 @@ func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error)
return nil, nil, err return nil, nil, err
} }
// Добавляем путь и имя только что собранного пакета в
// соответствующие срезы
builtPaths = append(builtPaths, pkgPath)
builtNames = append(builtNames, vars.Name)
// Удаляем дубликаты из pkgPaths и pkgNames. // Удаляем дубликаты из pkgPaths и pkgNames.
// Дубликаты могут появиться, если несколько зависимостей // Дубликаты могут появиться, если несколько зависимостей
// зависят от одних и тех же пакетов. // зависят от одних и тех же пакетов.
@@ -307,9 +244,7 @@ func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error)
// чтобы извлечь переменные сборки без выполнения реального кода. // чтобы извлечь переменные сборки без выполнения реального кода.
func (b *Builder) executeFirstPass( func (b *Builder) executeFirstPass(
fl *syntax.File, fl *syntax.File,
) (string, []*types.BuildVars, error) { ) (*types.BuildVars, error) {
varsOfPackages := []*types.BuildVars{}
scriptDir := filepath.Dir(b.opts.Script) // Получаем директорию скрипта scriptDir := filepath.Dir(b.opts.Script) // Получаем директорию скрипта
env := createBuildEnvVars(b.info, types.Directories{ScriptDir: scriptDir}) // Создаём переменные окружения для сборки env := createBuildEnvVars(b.info, types.Directories{ScriptDir: scriptDir}) // Создаём переменные окружения для сборки
@@ -317,17 +252,17 @@ func (b *Builder) executeFirstPass(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение
interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий interp.ReadDirHandler(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий
interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов
) )
if err != nil { if err != nil {
return "", nil, err return nil, err
} }
err = runner.Run(b.ctx, fl) // Запускаем скрипт err = runner.Run(b.ctx, fl) // Запускаем скрипт
if err != nil { if err != nil {
return "", nil, err return nil, err
} }
dec := decoder.New(b.info, runner) // Создаём новый декодер dec := decoder.New(b.info, runner) // Создаём новый декодер
@@ -340,65 +275,53 @@ func (b *Builder) executeFirstPass(
var pkgs packages var pkgs packages
err = dec.DecodeVars(&pkgs) err = dec.DecodeVars(&pkgs)
if err != nil { if err != nil {
return "", nil, err return nil, err
} }
if len(pkgs.Names) == 0 { if len(pkgs.Names) == 0 {
return "", nil, errors.New("package name is missing") return nil, errors.New("package name is missing")
} }
var vars types.BuildVars var vars types.BuildVars
if len(pkgs.Names) == 1 { if len(pkgs.Names) == 1 {
err = dec.DecodeVars(&vars) // Декодируем переменные err = dec.DecodeVars(&vars) // Декодируем переменные
if err != nil { if err != nil {
return "", nil, err return nil, err
} }
varsOfPackages = append(varsOfPackages, &vars) return &vars, nil
return vars.Name, varsOfPackages, nil
} }
if len(b.opts.Packages) == 0 { if b.opts.Package == "" {
return "", nil, errors.New("script has multiple packages but package is not specified") return nil, errors.New("script has multiple packages but package is not specified")
} }
for _, pkgName := range b.opts.Packages { var preVars types.BuildVarsPre
var preVars types.BuildVarsPre funcName := fmt.Sprintf("meta_%s", b.opts.Package)
funcName := fmt.Sprintf("meta_%s", pkgName) meta, ok := dec.GetFuncSub(funcName)
meta, ok := dec.GetFuncWithSubshell(funcName) if !ok {
if !ok { return nil, errors.New("func is missing")
return "", nil, errors.New("func is missing")
}
r, err := meta(b.ctx)
if err != nil {
return "", nil, err
}
d := decoder.New(&distro.OSRelease{}, r)
err = d.DecodeVars(&preVars)
if err != nil {
return "", nil, err
}
vars := preVars.ToBuildVars()
vars.Name = pkgName
vars.Base = pkgs.BasePkgName
varsOfPackages = append(varsOfPackages, &vars)
} }
r, err := meta(b.ctx)
if err != nil {
return nil, err
}
d := decoder.New(&distro.OSRelease{}, r)
err = d.DecodeVars(&preVars)
if err != nil {
return nil, err
}
vars = preVars.ToBuildVars()
vars.Name = b.opts.Package
return pkgs.BasePkgName, varsOfPackages, nil // Возвращаем переменные сборки return &vars, nil // Возвращаем переменные сборки
} }
// Функция getDirs возвращает соответствующие директории для скрипта // Функция getDirs возвращает соответствующие директории для скрипта
func (b *Builder) getDirs(basePkg string) (types.Directories, error) { func (b *Builder) getDirs(vars *types.BuildVars) types.Directories {
scriptPath, err := filepath.Abs(b.opts.Script) baseDir := filepath.Join(b.config.GetPaths(b.ctx).PkgsDir, vars.Name) // Определяем базовую директорию
if err != nil {
return types.Directories{}, err
}
baseDir := filepath.Join(b.config.GetPaths().PkgsDir, basePkg) // Определяем базовую директорию
return types.Directories{ return types.Directories{
BaseDir: baseDir, BaseDir: baseDir,
SrcDir: filepath.Join(baseDir, "src"), SrcDir: filepath.Join(baseDir, "src"),
PkgDir: filepath.Join(baseDir, "pkg"), PkgDir: filepath.Join(baseDir, "pkg"),
ScriptDir: filepath.Dir(scriptPath), ScriptDir: filepath.Dir(b.opts.Script),
}, nil }
} }
// Функция executeSecondPass выполняет скрипт сборки второй раз без каких-либо ограничений. Возвращается декодер, // Функция executeSecondPass выполняет скрипт сборки второй раз без каких-либо ограничений. Возвращается декодер,
@@ -412,11 +335,9 @@ func (b *Builder) executeSecondPass(
fakeroot := handlers.FakerootExecHandler(2 * time.Second) // Настраиваем "fakeroot" для выполнения fakeroot := handlers.FakerootExecHandler(2 * time.Second) // Настраиваем "fakeroot" для выполнения
runner, err := interp.New( runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandlers(func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc { interp.ExecHandler(helpers.Helpers.ExecHandler(fakeroot)), // Обрабатываем выполнение через fakeroot
return helpers.Helpers.ExecHandler(fakeroot)
}), // Обрабатываем выполнение через fakeroot
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -460,10 +381,10 @@ func (b *Builder) performChecks(ctx context.Context, vars *types.BuildVars, inst
// Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает // Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает
// срез, содержащий имена всех установленных пакетов. // срез, содержащий имена всех установленных пакетов.
func (b *Builder) installBuildDeps(ctx context.Context, buildDepends []string) ([]string, error) { func (b *Builder) installBuildDeps(ctx context.Context, vars *types.BuildVars) ([]string, error) {
var buildDeps []string var buildDeps []string
if len(buildDepends) > 0 { if len(vars.BuildDepends) > 0 {
deps, err := removeAlreadyInstalled(b.opts, buildDepends) deps, err := removeAlreadyInstalled(b.opts, vars.BuildDepends)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -482,45 +403,11 @@ func (b *Builder) installBuildDeps(ctx context.Context, buildDepends []string) (
return buildDeps, nil return buildDeps, nil
} }
func (b *Builder) getBuildersForPackages(pkgs []db.Package) []*Builder { func (b *Builder) buildALRDeps(ctx context.Context, vars *types.BuildVars) (builtPaths, builtNames, repoDeps []string, err error) {
type item struct { if len(vars.Depends) > 0 {
pkg *db.Package
packages []string
}
pkgsMap := make(map[string]*item)
for _, pkg := range pkgs {
name := pkg.BasePkgName
if name == "" {
name = pkg.Name
}
if pkgsMap[name] == nil {
pkgsMap[name] = &item{
pkg: &pkg,
}
}
pkgsMap[name].packages = append(
pkgsMap[name].packages,
pkg.Name,
)
}
builders := []*Builder{}
for basePkgName := range pkgsMap {
pkg := pkgsMap[basePkgName].pkg
builder := *b
builder.UpdateOptsFromPkg(pkg, pkgsMap[basePkgName].packages)
builders = append(builders, &builder)
}
return builders
}
func (b *Builder) buildALRDeps(ctx context.Context, depends []string) (builtPaths, builtNames, repoDeps []string, err error) {
if len(depends) > 0 {
slog.Info(gotext.Get("Installing dependencies")) slog.Info(gotext.Get("Installing dependencies"))
found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей found, notFound, err := b.repos.FindPkgs(ctx, vars.Depends) // Поиск зависимостей
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@@ -528,10 +415,14 @@ func (b *Builder) buildALRDeps(ctx context.Context, depends []string) (builtPath
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез // Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
pkgs := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive) pkgs := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive)
builders := b.getBuildersForPackages(pkgs)
for _, builder := range builders { for _, pkg := range pkgs {
newB := *b
newB.UpdateOptsFromPkg(&pkg)
// Собираем зависимости // Собираем зависимости
pkgPaths, pkgNames, err := builder.BuildPackage(ctx) pkgPaths, pkgNames, err := newB.BuildPackage(ctx)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@@ -540,6 +431,8 @@ func (b *Builder) buildALRDeps(ctx context.Context, depends []string) (builtPath
builtPaths = append(builtPaths, pkgPaths...) builtPaths = append(builtPaths, pkgPaths...)
// Добавляем пути всех собранных пакетов в builtPaths // Добавляем пути всех собранных пакетов в builtPaths
builtNames = append(builtNames, pkgNames...) builtNames = append(builtNames, pkgNames...)
// Добавляем имя текущего пакета в builtNames
builtNames = append(builtNames, filepath.Base(filepath.Dir(newB.opts.Script)))
} }
} }
@@ -552,6 +445,11 @@ func (b *Builder) buildALRDeps(ctx context.Context, depends []string) (builtPath
} }
func (b *Builder) getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error { func (b *Builder) getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error {
if len(bv.Sources) != len(bv.Checksums) {
slog.Error(gotext.Get("The checksums array must be the same length as sources"))
os.Exit(1)
}
for i, src := range bv.Sources { for i, src := range bv.Sources {
opts := dl.Options{ opts := dl.Options{
Name: fmt.Sprintf("%s[%d]", bv.Name, i), Name: fmt.Sprintf("%s[%d]", bv.Name, i),
@@ -632,33 +530,32 @@ func (b *Builder) executeFunctions(
ctx context.Context, ctx context.Context,
dec *decoder.Decoder, dec *decoder.Decoder,
dirs types.Directories, dirs types.Directories,
) error { vars *types.BuildVars,
/* ) (*FunctionsOutput, error) {
version, ok := dec.GetFunc("version") version, ok := dec.GetFunc("version")
if ok { if ok {
slog.Info(gotext.Get("Executing version()")) slog.Info(gotext.Get("Executing version()"))
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
err := version( err := version(
ctx, ctx,
interp.Dir(dirs.SrcDir), interp.Dir(dirs.SrcDir),
interp.StdIO(os.Stdin, buf, os.Stderr), interp.StdIO(os.Stdin, buf, os.Stderr),
) )
if err != nil { if err != nil {
return nil, err return nil, err
}
newVer := strings.TrimSpace(buf.String())
err = setVersion(ctx, dec.Runner, newVer)
if err != nil {
return nil, err
}
vars.Version = newVer
slog.Info(gotext.Get("Updating version"), "new", newVer)
} }
*/
newVer := strings.TrimSpace(buf.String())
err = setVersion(ctx, dec.Runner, newVer)
if err != nil {
return nil, err
}
vars.Version = newVer
slog.Info(gotext.Get("Updating version"), "new", newVer)
}
prepare, ok := dec.GetFunc("prepare") prepare, ok := dec.GetFunc("prepare")
if ok { if ok {
@@ -666,7 +563,7 @@ func (b *Builder) executeFunctions(
err := prepare(ctx, interp.Dir(dirs.SrcDir)) err := prepare(ctx, interp.Dir(dirs.SrcDir))
if err != nil { if err != nil {
return err return nil, err
} }
} }
@@ -676,29 +573,15 @@ func (b *Builder) executeFunctions(
err := build(ctx, interp.Dir(dirs.SrcDir)) err := build(ctx, interp.Dir(dirs.SrcDir))
if err != nil { if err != nil {
return err return nil, err
} }
} }
return nil
}
func (b *Builder) executePackageFunctions(
ctx context.Context,
dec *decoder.Decoder,
dirs types.Directories,
packageName string,
) (*FunctionsOutput, error) {
output := &FunctionsOutput{}
var packageFuncName string var packageFuncName string
var filesFuncName string if b.opts.Package == "" {
if packageName == "" {
packageFuncName = "package" packageFuncName = "package"
filesFuncName = "files"
} else { } else {
packageFuncName = fmt.Sprintf("package_%s", packageName) packageFuncName = fmt.Sprintf("package_%s", b.opts.Package)
filesFuncName = fmt.Sprintf("files_%s", packageName)
} }
packageFn, ok := dec.GetFunc(packageFuncName) packageFn, ok := dec.GetFunc(packageFuncName)
if ok { if ok {
@@ -709,6 +592,14 @@ func (b *Builder) executePackageFunctions(
} }
} }
output := &FunctionsOutput{}
var filesFuncName string
if b.opts.Package == "" {
filesFuncName = "files"
} else {
filesFuncName = fmt.Sprintf("files_%s", b.opts.Package)
}
files, ok := dec.GetFuncP(filesFuncName, func(ctx context.Context, s *interp.Runner) error { files, ok := dec.GetFuncP(filesFuncName, func(ctx context.Context, s *interp.Runner) error {
// It should be done via interp.RunnerOption, // It should be done via interp.RunnerOption,
// but due to the issues below, it cannot be done. // but due to the issues below, it cannot be done.
@@ -745,8 +636,8 @@ func (b *Builder) executePackageFunctions(
return output, nil return output, nil
} }
func (b *Builder) installOptDeps(ctx context.Context, optDepends []string) error { func (b *Builder) installOptDeps(ctx context.Context, vars *types.BuildVars) error {
optDeps, err := removeAlreadyInstalled(b.opts, optDepends) optDeps, err := removeAlreadyInstalled(b.opts, vars.OptDepends)
if err != nil { if err != nil {
return err return err
} }
@@ -792,8 +683,10 @@ func (b *Builder) InstallPkgs(
} }
func (b *Builder) InstallALRPackages(ctx context.Context, pkgs []db.Package, opts types.BuildOpts) { func (b *Builder) InstallALRPackages(ctx context.Context, pkgs []db.Package, opts types.BuildOpts) {
builders := b.getBuildersForPackages(pkgs) for _, pkg := range pkgs {
for _, builder := range builders { builder := *b
builder.UpdateOptsFromPkg(&pkg)
builtPkgs, _, err := builder.BuildPackage(ctx) builtPkgs, _, err := builder.BuildPackage(ctx)
// Выполняем сборку пакета // Выполняем сборку пакета
if err != nil { if err != nil {
@@ -811,108 +704,3 @@ func (b *Builder) InstallALRPackages(ctx context.Context, pkgs []db.Package, opt
} }
} }
} }
// Функция buildPkgMetadata создает метаданные для пакета, который будет собран.
func (b *Builder) buildPkgMetadata(
ctx context.Context,
vars *types.BuildVars,
dirs types.Directories,
pkgFormat string,
deps []string,
preferedContents *[]string,
) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars, b.info, &b.opts)
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: append(vars.Conflicts, vars.Name),
Replaces: vars.Replaces,
Provides: append(vars.Provides, vars.Name),
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
}
// Функция checkForBuiltPackage пытается обнаружить ранее собранный пакет и вернуть его путь
// и true, если нашла. Если нет, возвратит "", false, nil.
func (b *Builder) checkForBuiltPackage(
vars *types.BuildVars,
pkgFormat,
baseDir string,
) (string, bool, error) {
filename, err := b.pkgFileName(vars, pkgFormat)
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
}
// 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 (b *Builder) pkgFileName(vars *types.BuildVars, pkgFormat string) (string, error) {
pkgInfo := getBasePkgInfo(vars, b.info, &b.opts)
packager, err := nfpm.Get(pkgFormat)
if err != nil {
return "", err
}
return packager.ConventionalFileName(pkgInfo), nil
}

View File

@@ -144,11 +144,11 @@ func (m *TestManager) IsInstalled(pkg string) (bool, error) {
type TestConfig struct{} type TestConfig struct{}
func (c *TestConfig) PagerStyle() string { func (c *TestConfig) PagerStyle(ctx context.Context) string {
return "native" return "native"
} }
func (c *TestConfig) GetPaths() *config.Paths { func (c *TestConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
CacheDir: "/tmp", CacheDir: "/tmp",
} }
@@ -191,7 +191,7 @@ rm -f %s`, tmpFilePath)
fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "alr.sh") fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "alr.sh")
assert.NoError(t, err) assert.NoError(t, err)
_, _, err = b.executeFirstPass(fl) _, err = b.executeFirstPass(fl)
assert.NoError(t, err) assert.NoError(t, err)
_, err = os.Stat(tmpFilePath) _, err = os.Stat(tmpFilePath)
@@ -203,7 +203,7 @@ func TestExecuteFirstPassIsCorrect(t *testing.T) {
Name string Name string
Script string Script string
Opts types.BuildOpts Opts types.BuildOpts
Expected func(t *testing.T, vars []*types.BuildVars) Expected func(t *testing.T, vars *types.BuildVars)
} }
for _, tc := range []testCase{{ for _, tc := range []testCase{{
@@ -220,13 +220,12 @@ maintainer='Ivan Ivanov'
Manager: &TestManager{}, Manager: &TestManager{},
Interactive: false, Interactive: false,
}, },
Expected: func(t *testing.T, vars []*types.BuildVars) { Expected: func(t *testing.T, vars *types.BuildVars) {
assert.Equal(t, 1, len(vars)) assert.Equal(t, vars.Name, "test")
assert.Equal(t, vars[0].Name, "test") assert.Equal(t, vars.Version, "1.0.0")
assert.Equal(t, vars[0].Version, "1.0.0") assert.Equal(t, vars.Release, int(1))
assert.Equal(t, vars[0].Release, int(1)) assert.Equal(t, vars.Epoch, uint(2))
assert.Equal(t, vars[0].Epoch, uint(2)) assert.Equal(t, vars.Description, "Test package")
assert.Equal(t, vars[0].Description, "Test package")
}, },
}, { }, {
Name: "multiple packages", Name: "multiple packages",
@@ -249,14 +248,13 @@ meta_bar() {
} }
`, `,
Opts: types.BuildOpts{ Opts: types.BuildOpts{
Packages: []string{"foo"}, Package: "foo",
Manager: &TestManager{}, Manager: &TestManager{},
Interactive: false, Interactive: false,
}, },
Expected: func(t *testing.T, vars []*types.BuildVars) { Expected: func(t *testing.T, vars *types.BuildVars) {
assert.Equal(t, 1, len(vars)) assert.Equal(t, vars.Name, "foo")
assert.Equal(t, vars[0].Name, "foo") assert.Equal(t, vars.Description, "Foo package")
assert.Equal(t, vars[0].Description, "Foo package")
}, },
}} { }} {
t.Run(tc.Name, func(t *testing.T) { t.Run(tc.Name, func(t *testing.T) {
@@ -277,10 +275,10 @@ meta_bar() {
fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh") fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh")
assert.NoError(t, err) assert.NoError(t, err)
_, allVars, err := b.executeFirstPass(fl) vars, err := b.executeFirstPass(fl)
assert.NoError(t, err) assert.NoError(t, err)
tc.Expected(t, allVars) tc.Expected(t, vars)
}) })
} }
} }

View File

@@ -17,11 +17,11 @@
package build package build
import ( import (
"fmt" "context"
"io" "io"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"slices" "slices"
"strconv" "strconv"
@@ -33,6 +33,8 @@ import (
_ "github.com/goreleaser/nfpm/v2/arch" _ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb" _ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm" _ "github.com/goreleaser/nfpm/v2/rpm"
"github.com/leonelquinteros/gotext"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
"github.com/goreleaser/nfpm/v2" "github.com/goreleaser/nfpm/v2"
@@ -41,6 +43,7 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/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/shutils/decoder"
"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/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
@@ -75,6 +78,79 @@ func prepareDirs(dirs types.Directories) error {
return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов 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)
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
})
}
pkgInfo.Release = overrides.ReleasePlatformSpecific(vars.Release, info)
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 создает секцию содержимого пакета, которая содержит файлы, // Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
// которые будут включены в конечный пакет. // которые будут включены в конечный пакет.
func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) { func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
@@ -171,18 +247,47 @@ func buildContents(vars *types.BuildVars, dirs types.Directories, preferedConten
return contents, nil return contents, nil
} }
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`) // Функция checkForBuiltPackage пытается обнаружить ранее собранный пакет и вернуть его путь
// и true, если нашла. Если нет, возвратит "", false, nil.
func checkForBuiltPackage(mgr manager.Manager, vars *types.BuildVars, pkgFormat, baseDir string) (string, bool, error) {
filename, err := pkgFileName(vars, pkgFormat)
if err != nil {
return "", false, err
}
func getBasePkgInfo(vars *types.BuildVars, info *distro.OSRelease, opts *types.BuildOpts) *nfpm.Info { pkgPath := filepath.Join(baseDir, filename)
_, err = os.Stat(pkgPath)
if err != nil {
return "", false, nil
}
return pkgPath, true, nil
}
func getBasePkgInfo(vars *types.BuildVars) *nfpm.Info {
return &nfpm.Info{ return &nfpm.Info{
Name: fmt.Sprintf("%s+alr-%s", vars.Name, opts.Repository), Name: vars.Name,
Arch: cpu.Arch(), Arch: cpu.Arch(),
Version: vars.Version, Version: vars.Version,
Release: overrides.ReleasePlatformSpecific(vars.Release, info), Release: strconv.Itoa(vars.Release),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10), 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) (string, error) {
pkgInfo := getBasePkgInfo(vars)
packager, err := nfpm.Get(pkgFormat)
if err != nil {
return "", err
}
return packager.ConventionalFileName(pkgInfo), nil
}
// Функция getPkgFormat возвращает формат пакета из менеджера пакетов, // Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// или ALR_PKG_FORMAT, если он установлен. // или ALR_PKG_FORMAT, если он установлен.
func getPkgFormat(mgr manager.Manager) string { func getPkgFormat(mgr manager.Manager) string {
@@ -261,7 +366,6 @@ func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) {
} }
} }
/*
// Функция setVersion изменяет переменную версии в скрипте runner. // Функция setVersion изменяет переменную версии в скрипте runner.
// Она используется для установки версии на вывод функции version(). // Она используется для установки версии на вывод функции version().
func setVersion(ctx context.Context, r *interp.Runner, to string) error { func setVersion(ctx context.Context, r *interp.Runner, to string) error {
@@ -271,7 +375,7 @@ func setVersion(ctx context.Context, r *interp.Runner, to string) error {
} }
return r.Run(ctx, fl) return r.Run(ctx, fl)
} }
*/
// Returns not installed dependencies // Returns not installed dependencies
func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) { func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) {
filteredPackages := []string{} filteredPackages := []string{}
@@ -313,24 +417,3 @@ func removeDuplicates(slice []string) []string {
return result return result
} }
func removeDuplicatesSources(sources, checksums []string) ([]string, []string) {
seen := map[string]string{}
keys := make([]string, 0)
for i, s := range sources {
if val, ok := seen[s]; !ok || strings.EqualFold(val, "SKIP") {
if !ok {
keys = append(keys, s)
}
seen[s] = checksums[i]
}
}
newSources := make([]string, len(keys))
newChecksums := make([]string, len(keys))
for i, k := range keys {
newSources[i] = k
newChecksums[i] = seen[k]
}
return newSources, newChecksums
}

View File

@@ -1,47 +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 (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRemoveDuplicatesSources(t *testing.T) {
type testCase struct {
Name string
Sources []string
Checksums []string
NewSources []string
NewChecksums []string
}
for _, tc := range []testCase{{
Name: "prefer non-skip values",
Sources: []string{"a", "b", "c", "a"},
Checksums: []string{"skip", "skip", "skip", "1"},
NewSources: []string{"a", "b", "c"},
NewChecksums: []string{"1", "skip", "skip"},
}} {
t.Run(tc.Name, func(t *testing.T) {
s, c := removeDuplicatesSources(tc.Sources, tc.Checksums)
assert.Equal(t, s, tc.NewSources)
assert.Equal(t, c, tc.NewChecksums)
})
}
}

View File

@@ -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()),
) )

View File

@@ -32,19 +32,19 @@ deps=("python3")
deps_arch=("python") deps_arch=("python")
deps_alpine=("python3") deps_alpine=("python3")
build_deps=("python3" "python3-pip") build_deps=("python3" "python3-setuptools")
build_deps_arch=("python" "python-pip") build_deps_arch=("python" "python-setuptools")
build_deps_alpine=("python3" "py3-pip") build_deps_alpine=("python3" "py3-setuptools")
sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}") sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}")
checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}') checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}')
build() { build() {
cd "$srcdir/{{.Info.Name}}-${version}" cd "$srcdir/{{.Info.Name}}-${version}"
python3 -m build python3 setup.py build
} }
package() { package() {
cd "$srcdir/{{.Info.Name}}-${version}" cd "$srcdir/{{.Info.Name}}-${version}"
pip install --root="${pkgdir}/" . --no-deps --disable-pip-version-check python3 setup.py install --root="${pkgdir}/" --optimize=1 || return 1
} }

View File

@@ -67,7 +67,7 @@ type action struct {
// If repos is set to nil, the repos in the ALR config will be used. // If repos is set to nil, the repos in the ALR config will be used.
func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
if repos == nil { if repos == nil {
repos = rs.cfg.Repos() repos = rs.cfg.Repos(ctx)
} }
for _, repo := range repos { for _, repo := range repos {
@@ -77,7 +77,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
} }
slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) slog.Info(gotext.Get("Pulling repository"), "name", repo.Name)
repoDir := filepath.Join(rs.cfg.GetPaths().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")
@@ -223,7 +223,7 @@ func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.
if err != nil { if err != nil {
return err return err
} }
meta, ok := d.GetFuncWithSubshell(funcName) meta, ok := d.GetFuncSub(funcName)
if !ok { if !ok {
return errors.New("func is missing") return errors.New("func is missing")
} }
@@ -264,7 +264,7 @@ func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Ru
return interp.New( return interp.New(
interp.Env(expand.ListEnviron(env...)), interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec), interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler2(handlers.RestrictedReadDir(repoDir)), interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)), interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)), interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}), interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),

View File

@@ -32,13 +32,13 @@ import (
type TestALRConfig struct{} type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
DBPath: ":memory:", DBPath: ":memory:",
} }
} }
func (c *TestALRConfig) Repos() []types.Repo { func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo {
return []types.Repo{ return []types.Repo{
{ {
Name: "test", Name: "test",

View File

@@ -44,7 +44,7 @@ type TestALRConfig struct {
PkgsDir string PkgsDir string
} }
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
DBPath: ":memory:", DBPath: ":memory:",
CacheDir: c.CacheDir, CacheDir: c.CacheDir,
@@ -53,7 +53,7 @@ func (c *TestALRConfig) GetPaths() *config.Paths {
} }
} }
func (c *TestALRConfig) Repos() []types.Repo { func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo {
return []types.Repo{} return []types.Repo{}
} }

View File

@@ -17,14 +17,16 @@
package repos package repos
import ( import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
type Config interface { type Config interface {
GetPaths() *config.Paths GetPaths(ctx context.Context) *config.Paths
Repos() []types.Repo Repos(ctx context.Context) []types.Repo
} }
type Repos struct { type Repos struct {

69
pkg/repos/repos_legacy.go Normal file
View File

@@ -0,0 +1,69 @@
// 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"
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][]database.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
}

View File

@@ -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
} }

View File

@@ -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
}

View File

@@ -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)
})
}
}

82
repo.go
View File

@@ -25,11 +25,12 @@ import (
"path/filepath" "path/filepath"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"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"
) )
@@ -59,42 +60,33 @@ 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)
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
reposSlice := cfg.Repos() for _, repo := range cfg.Repos {
for _, repo := range reposSlice {
if repo.URL == repoURL { if repo.URL == repoURL {
slog.Error("Repo already exists", "name", repo.Name) slog.Error("Repo already exists", "name", repo.Name)
os.Exit(1) os.Exit(1)
} }
} }
reposSlice = append(reposSlice, types.Repo{ cfg.Repos = append(cfg.Repos, types.Repo{
Name: name, Name: name,
URL: repoURL, URL: repoURL,
}) })
cfg.SetRepos(reposSlice)
err = cfg.SaveUserConfig() cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error saving config"), "err", err) slog.Error(gotext.Get("Error opening config file"), "err", err)
os.Exit(1) os.Exit(1)
} }
db := database.New(cfg) err = toml.NewEncoder(cfgFl).Encode(cfg)
err = db.Init(ctx)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err) slog.Error(gotext.Get("Error encoding config"), "err", err)
os.Exit(1)
} }
rs := repos.New(cfg, db) err = repos.Pull(ctx, cfg.Repos)
err = rs.Pull(ctx, cfg.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)
@@ -122,17 +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)
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
found := false found := false
index := 0 index := 0
reposSlice := cfg.Repos() 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
@@ -143,25 +129,26 @@ func RemoveRepoCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
cfg.SetRepos(slices.Delete(reposSlice, index, index+1)) cfg.Repos = slices.Delete(cfg.Repos, index, index+1)
err = os.RemoveAll(filepath.Join(cfg.GetPaths().RepoDir, name)) cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath)
if err != nil {
slog.Error(gotext.Get("Error opening config file"), "err", err)
os.Exit(1)
}
err = toml.NewEncoder(cfgFl).Encode(&cfg)
if err != nil {
slog.Error(gotext.Get("Error encoding config"), "err", err)
os.Exit(1)
}
err = os.RemoveAll(filepath.Join(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)
} }
err = cfg.SaveUserConfig()
if err != nil {
slog.Error(gotext.Get("Error saving config"), "err", err)
os.Exit(1)
}
db := database.New(cfg)
err = db.Init(ctx)
if err != nil {
os.Exit(1)
}
err = db.DeletePkgs(ctx, "repository = ?", name) err = db.DeletePkgs(ctx, "repository = ?", name)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error removing packages from database"), "err", err) slog.Error(gotext.Get("Error removing packages from database"), "err", err)
@@ -180,20 +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)
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
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())
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)

View File

@@ -25,7 +25,7 @@ elif (( $(echo "$COVERAGE < 80" | bc -l) )); then
COLOR="#dfb317" COLOR="#dfb317"
fi fi
cat <<EOF > assets/coverage-badge.svg 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"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="109" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/> <linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient> <stop offset="1" stop-opacity=".1"/></linearGradient>

View File

@@ -1,84 +0,0 @@
#!/bin/bash
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
TRANSLATIONS_DIR="internal/translations/po"
if [ ! -d "$TRANSLATIONS_DIR" ]; then
echo "Error: directory '$TRANSLATIONS_DIR' not found"
exit 1
fi
declare -A TOTAL_STRINGS_MAP
declare -A TRANSLATED_STRINGS_MAP
for PO_FILE in $(find "$TRANSLATIONS_DIR" -type f -name "*.po"); do
LANG_DIR=$(dirname "$PO_FILE")
LANG=$(basename "$LANG_DIR")
STATS=$(LC_ALL=C msgfmt --statistics -o /dev/null "$PO_FILE" 2>&1)
NUMBERS=($(echo "$STATS" | grep -o '[0-9]\+'))
case ${#NUMBERS[@]} in
1) TRANSLATED_STRINGS=${NUMBERS[0]}; UNTRANSLATED_STRINGS=0 ;; # all translated
2) TRANSLATED_STRINGS=${NUMBERS[0]}; UNTRANSLATED_STRINGS=${NUMBERS[1]} ;; # no fuzzy
3) TRANSLATED_STRINGS=${NUMBERS[0]}; UNTRANSLATED_STRINGS=${NUMBERS[2]} ;; # with fuzzy
*) TRANSLATED_STRINGS=0; UNTRANSLATED_STRINGS=0 ;;
esac
TOTAL_STRINGS=$((TRANSLATED_STRINGS + UNTRANSLATED_STRINGS))
TOTAL_STRINGS_MAP[$LANG]=$((TOTAL_STRINGS_MAP[$LANG] + TOTAL_STRINGS))
TRANSLATED_STRINGS_MAP[$LANG]=$((TRANSLATED_STRINGS_MAP[$LANG] + TRANSLATED_STRINGS))
done
for LANG in "${!TOTAL_STRINGS_MAP[@]}"; do
TOTAL=${TOTAL_STRINGS_MAP[$LANG]}
TRANSLATED=${TRANSLATED_STRINGS_MAP[$LANG]}
if [ "$TOTAL" -eq 0 ]; then
PERCENTAGE="0.00"
else
PERCENTAGE=$(echo "scale=2; ($TRANSLATED / $TOTAL) * 100" | bc)
fi
COLOR="#4c1"
if (( $(echo "$PERCENTAGE < 50" | bc -l) )); then
COLOR="#e05d44"
elif (( $(echo "$PERCENTAGE < 80" | bc -l) )); then
COLOR="#dfb317"
fi
cat <<EOF > assets/i18n-$LANG-badge.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="129" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)">
<rect width="75" height="20" fill="#555"/>
<rect x="75" width="64" height="20" fill="${COLOR}"/>
<rect width="129" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">$LANG translate</text>
<text x="37" y="14">$LANG translate</text>
<text x="100" y="15" fill="#010101" fill-opacity=".3">${PERCENTAGE}%</text>
<text x="100" y="14">${PERCENTAGE}%</text>
</g>
</svg>
EOF
done

125
search.go
View File

@@ -1,125 +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()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
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
},
}
}

View File

@@ -29,6 +29,7 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.elara.ws/vercmp" "go.elara.ws/vercmp"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
@@ -38,7 +39,6 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
) )
func UpgradeCmd() *cli.Command { func UpgradeCmd() *cli.Command {
@@ -57,17 +57,11 @@ func UpgradeCmd() *cli.Command {
ctx := c.Context ctx := c.Context
cfg := config.New() cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg) db := database.New(cfg)
rs := repos.New(cfg, db) rs := repos.New(cfg, db)
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 db init"), "err", err)
os.Exit(1) os.Exit(1)
} }
@@ -83,15 +77,15 @@ func UpgradeCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
if cfg.AutoPull() { if cfg.AutoPull(ctx) {
err = rs.Pull(ctx, cfg.Repos()) err = rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err) slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1) os.Exit(1)
} }
} }
updates, err := checkForUpdates(ctx, mgr, cfg, db, rs, info) updates, err := checkForUpdates(ctx, mgr, cfg, rs, info)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error checking for updates"), "err", err) slog.Error(gotext.Get("Error checking for updates"), "err", err)
os.Exit(1) os.Exit(1)
@@ -127,7 +121,6 @@ func checkForUpdates(
ctx context.Context, ctx context.Context,
mgr manager.Manager, mgr manager.Manager,
cfg *config.ALRConfig, cfg *config.ALRConfig,
db *database.Database,
rs *repos.Repos, rs *repos.Repos,
info *distro.OSRelease, info *distro.OSRelease,
) ([]database.Package, error) { ) ([]database.Package, error) {
@@ -137,51 +130,42 @@ func checkForUpdates(
} }
pkgNames := maps.Keys(installed) pkgNames := maps.Keys(installed)
found, _, err := rs.FindPkgs(ctx, pkgNames)
s := search.New(db) if err != nil {
return nil, err
var out []database.Package
for _, pkgName := range pkgNames {
matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
if matches != nil {
packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")]
repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")]
pkgs, err := s.Search(
ctx,
search.NewSearchOptions().
WithName(packageName).
WithRepository(repoName).
Build(),
)
if err != nil {
return nil, err
}
if len(pkgs) == 0 {
continue
}
pkg := pkgs[0]
repoVer := pkg.Version
releaseStr := overrides.ReleasePlatformSpecific(pkg.Release, info)
if pkg.Release != 0 && pkg.Epoch == 0 {
repoVer = fmt.Sprintf("%s-%s", pkg.Version, releaseStr)
} else if pkg.Release != 0 && pkg.Epoch != 0 {
repoVer = fmt.Sprintf("%d:%s-%s", pkg.Epoch, pkg.Version, releaseStr)
}
c := vercmp.Compare(repoVer, installed[pkgName])
if c == 0 || c == -1 {
continue
} else if c == 1 {
out = append(out, pkg)
}
}
} }
var out []database.Package
for pkgName, pkgs := range found {
if slices.Contains(cfg.IgnorePkgUpdates(ctx), pkgName) {
continue
}
if len(pkgs) > 1 {
// Puts the element with the highest version first
slices.SortFunc(pkgs, func(a, b database.Package) int {
return vercmp.Compare(a.Version, b.Version)
})
}
// First element is the package we want to install
pkg := pkgs[0]
repoVer := pkg.Version
releaseStr := overrides.ReleasePlatformSpecific(pkg.Release, info)
if pkg.Release != 0 && pkg.Epoch == 0 {
repoVer = fmt.Sprintf("%s-%s", pkg.Version, releaseStr)
} else if pkg.Release != 0 && pkg.Epoch != 0 {
repoVer = fmt.Sprintf("%d:%s-%s", pkg.Epoch, pkg.Version, releaseStr)
}
c := vercmp.Compare(repoVer, installed[pkgName])
if c == 0 || c == -1 {
continue
} else if c == 1 {
out = append(out, pkg)
}
}
return out, nil return out, nil
} }