Compare commits

..

No commits in common. "master" and "feat/files-function" have entirely different histories.

44 changed files with 1295 additions and 2843 deletions

4
.gitignore vendored

@ -5,6 +5,4 @@
/internal/config/version.txt /internal/config/version.txt
.fleet .fleet
.idea .idea
.gigaide .gigaide
*.out

@ -43,8 +43,4 @@ issues:
exclude-rules: exclude-rules:
- path: _test\.go - path: _test\.go
linters: linters:
- errcheck - errcheck
# TODO: remove
- linters:
- staticcheck
text: "SA1019:"

@ -1,42 +0,0 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
repos:
- repo: local
hooks:
- id: test-coverage
name: Run test coverage
entry: make test-coverage
language: system
pass_filenames: false
- id: fmt
name: Format code
entry: make fmt
language: system
pass_filenames: false
- id: update-license
name: Update license
entry: make update-license
language: system
pass_filenames: false
- id: i18n
name: Update i18n
entry: make i18n
language: system
pass_filenames: false

@ -12,7 +12,7 @@ INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completio
INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME) INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME)
ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524 ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524
GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0 XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0
.PHONY: build install clean clear uninstall check-no-root .PHONY: build install clean clear uninstall check-no-root
@ -65,8 +65,4 @@ fmt:
i18n: i18n:
$(XGOTEXT_BIN) --output ./internal/translations/default.pot $(XGOTEXT_BIN) --output ./internal/translations/default.pot
msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot
msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot
test-coverage:
go test ./... -v -coverpkg=./... -coverprofile=coverage.out
bash scripts/coverage-badge.sh

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

@ -23,17 +23,14 @@ import (
"log/slog" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
@ -49,11 +46,6 @@ func BuildCmd() *cli.Command {
Value: "alr.sh", Value: "alr.sh",
Usage: gotext.Get("Path to the build script"), Usage: gotext.Get("Path to the build script"),
}, },
&cli.StringFlag{
Name: "script-package",
Aliases: []string{"sp"},
Usage: gotext.Get("Specify package in script (for multi package script only)"),
},
&cli.StringFlag{ &cli.StringFlag{
Name: "package", Name: "package",
Aliases: []string{"p"}, Aliases: []string{"p"},
@ -67,106 +59,43 @@ func BuildCmd() *cli.Command {
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
cfg := config.New()
db := database.New(cfg) script := c.String("script")
rs := repos.New(cfg, db) if c.String("package") != "" {
err := db.Init(ctx) script = filepath.Join(config.GetPaths(ctx).RepoDir, c.String("package"), "alr.sh")
if err != nil {
slog.Error(gotext.Get("Error db init"), "err", err)
os.Exit(1)
} }
var script string if config.GetInstance(ctx).AutoPull(ctx) {
var packages []string err := repos.Pull(ctx, config.Config(ctx).Repos)
// Проверяем, установлен ли флаг script (-s)
repoDir := cfg.GetPaths(ctx).RepoDir
switch {
case c.IsSet("script"):
script = c.String("script")
packages = append(packages, c.String("script-package"))
case c.IsSet("package"):
// TODO: handle multiple packages
packageInput := c.String("package")
arr := strings.Split(packageInput, "/")
var packageSearch string
if len(arr) == 2 {
packageSearch = arr[1]
} else {
packageSearch = arr[0]
}
pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch})
pkg, ok := pkgs[packageSearch]
if len(pkg) < 1 || !ok {
slog.Error(gotext.Get("Package not found"))
os.Exit(1)
}
if pkg[0].BasePkgName != "" {
script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].BasePkgName, "alr.sh")
packages = append(packages, pkg[0].Name)
} else {
script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].Name, "alr.sh")
}
default:
script = filepath.Join(repoDir, "alr.sh")
}
// Проверка автоматического пулла репозиториев
if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err) slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1) os.Exit(1)
} }
} }
// Обнаружение менеджера пакетов
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1) os.Exit(1)
} }
info, err := distro.ParseOSRelease(ctx) pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{
if err != nil { Script: script,
slog.Error(gotext.Get("Error parsing os release"), "err", err) Manager: mgr,
os.Exit(1) Clean: c.Bool("clean"),
} Interactive: c.Bool("interactive"),
})
builder := build.NewBuilder(
ctx,
types.BuildOpts{
Packages: packages,
Script: script,
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
rs,
info,
cfg,
)
// Сборка пакета
pkgPaths, _, err := builder.BuildPackage(ctx)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error building package"), "err", err) slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1) os.Exit(1)
} }
// Получение текущей рабочей директории
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err) slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1) os.Exit(1)
} }
// Перемещение собранных пакетов в рабочую директорию
for _, pkgPath := range pkgPaths { for _, pkgPath := range pkgPaths {
name := filepath.Base(pkgPath) name := filepath.Base(pkgPath)
err = osutils.Move(pkgPath, filepath.Join(wd, name)) err = osutils.Move(pkgPath, filepath.Join(wd, name))

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

Before

(image error) Size: 926 B

26
go.mod

@ -5,37 +5,37 @@ go 1.22
toolchain go1.23.5 toolchain go1.23.5
require ( require (
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0 github.com/PuerkitoBio/purell v1.2.0
github.com/alecthomas/chroma/v2 v2.9.1 github.com/alecthomas/chroma/v2 v2.9.1
github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v1.2.4 github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v1.0.0 github.com/charmbracelet/lipgloss v0.10.0
github.com/charmbracelet/log v0.4.0 github.com/charmbracelet/log v0.4.0
github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.12.0 github.com/go-git/go-git/v5 v5.12.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/goreleaser/nfpm/v2 v2.41.0 github.com/goreleaser/nfpm/v2 v2.41.0
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/leonelquinteros/gotext v1.7.0 github.com/leonelquinteros/gotext v1.7.0
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.19
github.com/mholt/archiver/v4 v4.0.0-alpha.8 github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/muesli/reflow v0.3.0 github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.1.0 github.com/pelletier/go-toml/v2 v2.1.0
github.com/schollz/progressbar/v3 v3.13.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/vmihailenco/msgpack/v5 v5.3.5
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
golang.org/x/crypto v0.27.0 golang.org/x/crypto v0.27.0
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
golang.org/x/sys v0.29.0 golang.org/x/sys v0.27.0
golang.org/x/text v0.21.0 golang.org/x/text v0.21.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.25.0 modernc.org/sqlite v1.25.0
mvdan.cc/sh/v3 v3.10.0 mvdan.cc/sh/v3 v3.10.0
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283
) )
require ( require (
@ -53,11 +53,9 @@ require (
github.com/bodgit/sevenzip v1.3.0 // indirect github.com/bodgit/sevenzip v1.3.0 // indirect
github.com/bodgit/windows v1.0.0 // indirect github.com/bodgit/windows v1.0.0 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.4.5 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.3.8 // indirect github.com/cloudflare/circl v1.3.8 // indirect
github.com/connesc/cipherio v0.2.1 // indirect github.com/connesc/cipherio v0.2.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
@ -65,7 +63,6 @@ require (
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/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/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
@ -87,11 +84,12 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
@ -115,7 +113,7 @@ require (
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.10.0 // indirect golang.org/x/sync v0.10.0 // indirect
golang.org/x/term v0.28.0 // indirect golang.org/x/term v0.25.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/tools v0.22.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
lukechampine.com/uint128 v1.2.0 // indirect lukechampine.com/uint128 v1.2.0 // indirect

77
go.sum

@ -17,8 +17,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1 h1:c7F4OsyQbiVpSOrYGMrNsRL37BwoOfrgoKxAwULBKZo=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
@ -75,20 +73,14 @@ github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTli
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -98,11 +90,14 @@ github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqw
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -121,8 +116,6 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
@ -139,8 +132,6 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@ -180,8 +171,6 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S3
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -221,6 +210,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
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=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
@ -252,13 +242,15 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-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=
@ -266,6 +258,8 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -274,8 +268,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
@ -305,13 +299,15 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
@ -330,6 +326,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -406,6 +403,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -428,6 +427,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -464,24 +465,26 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -522,6 +525,8 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -607,8 +612,12 @@ modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4= mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 h1:BXCLPeA8g2M6qYngicyxyB/2Bo4J54Q9Rb+8jMmE3ik=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283/go.mod h1:itzL9Jx52VXOhRaucFHuMpa3y7iwjnuLGdNvypoh/S4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

@ -29,10 +29,9 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
@ -64,52 +63,22 @@ func InstallCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
cfg := config.New() if config.GetInstance(ctx).AutoPull(ctx) {
db := database.New(cfg) err := repos.Pull(ctx, config.Config(ctx).Repos)
rs := repos.New(cfg, db)
err := db.Init(ctx)
if err != nil {
slog.Error(gotext.Get("Error db init"), "err", err)
os.Exit(1)
}
if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err) slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1) os.Exit(1)
} }
} }
found, notFound, err := rs.FindPkgs(ctx, args.Slice()) found, notFound, err := repos.FindPkgs(ctx, args.Slice())
if err != nil { if err != nil {
slog.Error(gotext.Get("Error finding packages"), "err", err) slog.Error(gotext.Get("Error finding packages"), "err", err)
os.Exit(1) os.Exit(1)
} }
pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive")) pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
opts := types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
}
info, err := distro.ParseOSRelease(ctx)
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
}
builder := build.NewBuilder(
ctx,
opts,
rs,
info,
cfg,
)
builder.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
Manager: mgr, Manager: mgr,
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
@ -117,8 +86,6 @@ func InstallCmd() *cli.Command {
return nil return nil
}, },
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {
cfg := config.New()
db := database.New(cfg)
result, err := db.GetPkgs(c.Context, "true") result, err := db.GetPkgs(c.Context, "true")
if err != nil { if err != nil {
slog.Error(gotext.Get("Error getting packages"), "err", err) slog.Error(gotext.Get("Error getting packages"), "err", err)
@ -127,7 +94,7 @@ func InstallCmd() *cli.Command {
defer result.Close() defer result.Close()
for result.Next() { for result.Next() {
var pkg database.Package var pkg db.Package
err = result.StructScan(&pkg) err = result.StructScan(&pkg)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error iterating over packages"), "err", err) slog.Error(gotext.Get("Error iterating over packages"), "err", err)

@ -45,8 +45,13 @@ var defaultConfig = &types.Config{
RootCmd: "sudo", RootCmd: "sudo",
PagerStyle: "native", PagerStyle: "native",
IgnorePkgUpdates: []string{}, IgnorePkgUpdates: []string{},
AutoPull: false, AutoPull: true,
Repos: []types.Repo{}, Repos: []types.Repo{
{
Name: "default",
URL: "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git",
},
},
} }
func New() *ALRConfig { func New() *ALRConfig {
@ -165,10 +170,3 @@ func (c *ALRConfig) AutoPull(ctx context.Context) bool {
}) })
return c.cfg.AutoPull return c.cfg.AutoPull
} }
func (c *ALRConfig) PagerStyle(ctx context.Context) string {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.PagerStyle
}

@ -42,19 +42,18 @@ var (
// Subsequent calls will just return the same value. // Subsequent calls will just return the same value.
func Language(ctx context.Context) language.Tag { func Language(ctx context.Context) language.Tag {
langMtx.Lock() langMtx.Lock()
defer langMtx.Unlock()
if !langSet { if !langSet {
syslang := SystemLang() syslang := SystemLang()
tag, err := language.Parse(syslang) tag, err := language.Parse(syslang)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error parsing system language"), "err", err) slog.Error(gotext.Get("Error parsing system language"), "err", err)
langMtx.Unlock()
os.Exit(1) os.Exit(1)
} }
base, _ := tag.Base() base, _ := tag.Base()
lang = language.Make(base.String()) lang = language.Make(base.String())
langSet = true langSet = true
} }
langMtx.Unlock()
return lang return lang
} }

@ -38,7 +38,7 @@ type Paths struct {
// using information from the system. // using information from the system.
// Subsequent calls will return the same value. // Subsequent calls will return the same value.
// //
// Deprecated: use struct API // Depreacted: use struct API
func GetPaths(ctx context.Context) *Paths { func GetPaths(ctx context.Context) *Paths {
alrConfig := GetInstance(ctx) alrConfig := GetInstance(ctx)
return alrConfig.GetPaths(ctx) return alrConfig.GetPaths(ctx)

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

@ -31,11 +31,10 @@ import (
// CurrentVersion is the current version of the database. // CurrentVersion is the current version of the database.
// The database is reset if its version doesn't match this. // The database is reset if its version doesn't match this.
const CurrentVersion = 3 const CurrentVersion = 2
// Package is a ALR package's database representation // Package is a ALR package's database representation
type Package struct { type Package struct {
BasePkgName string `sh:"base" db:"basepkg_name"`
Name string `sh:"name,required" db:"name"` Name string `sh:"name,required" db:"name"`
Version string `sh:"version,required" db:"version"` Version string `sh:"version,required" db:"version"`
Release int `sh:"release,required" db:"release"` Release int `sh:"release,required" db:"release"`
@ -100,7 +99,6 @@ func (d *Database) initDB(ctx context.Context) error {
conn := d.conn conn := d.conn
_, err := conn.ExecContext(ctx, ` _, err := conn.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS pkgs ( CREATE TABLE IF NOT EXISTS pkgs (
basepkg_name TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
repository TEXT NOT NULL, repository TEXT NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
@ -131,10 +129,7 @@ func (d *Database) initDB(ctx context.Context) error {
ver, ok := d.GetVersion(ctx) ver, ok := d.GetVersion(ctx)
if ok && ver != CurrentVersion { if ok && ver != CurrentVersion {
slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion)
err = d.reset(ctx) d.reset(ctx)
if err != nil {
return err
}
return d.initDB(ctx) return d.initDB(ctx)
} else if !ok { } else if !ok {
slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion) slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion)
@ -198,7 +193,6 @@ func (d *Database) IsEmpty(ctx context.Context) bool {
func (d *Database) InsertPackage(ctx context.Context, pkg Package) error { func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
_, err := d.conn.NamedExecContext(ctx, ` _, err := d.conn.NamedExecContext(ctx, `
INSERT OR REPLACE INTO pkgs ( INSERT OR REPLACE INTO pkgs (
basepkg_name,
name, name,
repository, repository,
version, version,
@ -216,7 +210,6 @@ func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
builddepends, builddepends,
optdepends optdepends
) VALUES ( ) VALUES (
:basepkg_name,
:name, :name,
:repository, :repository,
:version, :version,

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

@ -1,176 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package dl_test
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"os"
"path"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
)
type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{
CacheDir: "/tmp",
}
}
func TestDownloadWithoutCache(t *testing.T) {
type testCase struct {
name string
path string
expected func(*testing.T, error, string)
}
prepareServer := func() *httptest.Server {
// URL вашего Git-сервера
gitServerURL, err := url.Parse("https://gitea.plemya-x.ru")
if err != nil {
log.Fatalf("Failed to parse git server URL: %v", err)
}
proxy := httputil.NewSingleHostReverseProxy(gitServerURL)
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/file-downloader/file":
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, World!"))
case strings.HasPrefix(r.URL.Path, "/git-downloader/git"):
r.URL.Host = gitServerURL.Host
r.URL.Scheme = gitServerURL.Scheme
r.Host = gitServerURL.Host
r.URL.Path, _ = strings.CutPrefix(r.URL.Path, "/git-downloader/git")
proxy.ServeHTTP(w, r)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
}
for _, tc := range []testCase{
{
name: "simple file download",
path: "%s/file-downloader/file",
expected: func(t *testing.T, err error, tmpdir string) {
assert.NoError(t, err)
_, err = os.Stat(path.Join(tmpdir, "file"))
assert.NoError(t, err)
},
},
{
name: "git download",
path: "git+%s/git-downloader/git/Plemya-x/xpamych-alr-repo",
expected: func(t *testing.T, err error, tmpdir string) {
assert.NoError(t, err)
_, err = os.Stat(path.Join(tmpdir, "alr-repo.toml"))
assert.NoError(t, err)
},
},
} {
t.Run(tc.name, func(t *testing.T) {
server := prepareServer()
defer server.Close()
tmpdir, err := os.MkdirTemp("", "test-download")
assert.NoError(t, err)
defer os.RemoveAll(tmpdir)
opts := dl.Options{
CacheDisabled: true,
URL: fmt.Sprintf(tc.path, server.URL),
Destination: tmpdir,
}
err = dl.Download(context.Background(), opts)
tc.expected(t, err, tmpdir)
})
}
}
func TestDownloadFileWithCache(t *testing.T) {
type testCase struct {
name string
}
for _, tc := range []testCase{
{
name: "simple download",
},
} {
t.Run(tc.name, func(t *testing.T) {
called := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/file":
called += 1
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, World!"))
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()
tmpdir, err := os.MkdirTemp("", "test-download")
assert.NoError(t, err)
defer os.RemoveAll(tmpdir)
cfg := &TestALRConfig{}
opts := dl.Options{
CacheDisabled: false,
URL: server.URL + "/file",
Destination: tmpdir,
DlCache: dlcache.New(cfg),
}
outputFile := path.Join(tmpdir, "file")
err = dl.Download(context.Background(), opts)
assert.NoError(t, err)
_, err = os.Stat(outputFile)
assert.NoError(t, err)
err = os.Remove(outputFile)
assert.NoError(t, err)
err = dl.Download(context.Background(), opts)
assert.NoError(t, err)
assert.Equal(t, 1, called)
})
}
}

@ -22,7 +22,6 @@ package dl
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"io" "io"
"mime" "mime"
"net/http" "net/http"
@ -31,8 +30,12 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/mholt/archiver/v4" "github.com/mholt/archiver/v4"
"github.com/schollz/progressbar/v3"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
) )
// FileDownloader загружает файлы с использованием HTTP // FileDownloader загружает файлы с использованием HTTP
@ -51,7 +54,7 @@ func (FileDownloader) MatchURL(string) bool {
// Download загружает файл с использованием HTTP. Если файл // Download загружает файл с использованием HTTP. Если файл
// сжат в поддерживаемом формате, он будет распакован // сжат в поддерживаемом формате, он будет распакован
func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { func (FileDownloader) Download(opts Options) (Type, string, error) {
// Разбор URL // Разбор URL
u, err := url.Parse(opts.URL) u, err := url.Parse(opts.URL)
if err != nil { if err != nil {
@ -91,12 +94,8 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string,
} }
r = localFl r = localFl
} else { } else {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return 0, "", fmt.Errorf("failed to create request: %w", err)
}
// Выполнение HTTP GET запроса // Выполнение HTTP GET запроса
res, err := http.DefaultClient.Do(req) res, err := http.Get(u.String())
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
@ -115,15 +114,30 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string,
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
defer fl.Close()
var out io.WriteCloser var bar io.WriteCloser
// Настройка индикатора прогресса // Настройка индикатора прогресса
if opts.Progress != nil { if opts.Progress != nil {
out = NewProgressWriter(fl, size, name, opts.Progress) bar = progressbar.NewOptions64(
size,
progressbar.OptionSetDescription(name),
progressbar.OptionSetWriter(opts.Progress),
progressbar.OptionShowBytes(true),
progressbar.OptionSetWidth(10),
progressbar.OptionThrottle(65*time.Millisecond),
progressbar.OptionShowCount(),
progressbar.OptionOnCompletion(func() {
_, _ = io.WriteString(opts.Progress, "\n")
}),
progressbar.OptionSpinnerType(14),
progressbar.OptionFullWidth(),
progressbar.OptionSetRenderBlankState(true),
)
defer bar.Close()
} else { } else {
out = fl bar = handlers.NopRWC{}
} }
defer out.Close()
h, err := opts.NewHash() h, err := opts.NewHash()
if err != nil { if err != nil {
@ -133,9 +147,9 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string,
var w io.Writer var w io.Writer
// Настройка MultiWriter для записи в файл, хеш и индикатор прогресса // Настройка MultiWriter для записи в файл, хеш и индикатор прогресса
if opts.Hash != nil { if opts.Hash != nil {
w = io.MultiWriter(h, out) w = io.MultiWriter(fl, h, bar)
} else { } else {
w = io.MultiWriter(out) w = io.MultiWriter(fl, bar)
} }
// Копирование содержимого из источника в файл назначения // Копирование содержимого из источника в файл назначения

@ -20,7 +20,6 @@
package dl package dl
import ( import (
"context"
"errors" "errors"
"net/url" "net/url"
"path" "path"
@ -48,7 +47,7 @@ func (GitDownloader) MatchURL(u string) bool {
// Download uses git to clone the repository from the specified URL. // Download uses git to clone the repository from the specified URL.
// It allows specifying the revision, depth and recursion options // It allows specifying the revision, depth and recursion options
// via query string // via query string
func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { func (GitDownloader) Download(opts Options) (Type, string, error) {
u, err := url.Parse(opts.URL) u, err := url.Parse(opts.URL)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
@ -90,7 +89,7 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string,
co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
} }
r, err := git.PlainCloneContext(ctx, opts.Destination, false, co) r, err := git.PlainClone(opts.Destination, false, co)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }

@ -1,247 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package dl
import (
"fmt"
"io"
"math"
"os"
"time"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/leonelquinteros/gotext"
)
type model struct {
progress progress.Model
spinner spinner.Model
percent float64
speed float64
done bool
useSpinner bool
filename string
total int64
downloaded int64
elapsed time.Duration
remaining time.Duration
width int
}
func (m model) Init() tea.Cmd {
if m.useSpinner {
return m.spinner.Tick
}
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.done {
return m, tea.Quit
}
switch msg := msg.(type) {
case progressUpdate:
m.percent = msg.percent
m.speed = msg.speed
m.downloaded = msg.downloaded
m.total = msg.total
m.elapsed = time.Duration(msg.elapsed) * time.Second
m.remaining = time.Duration(msg.remaining) * time.Second
if m.percent >= 1.0 {
m.done = true
return m, tea.Quit
}
return m, nil
case tea.WindowSizeMsg:
m.width = msg.Width
return m, nil
case progress.FrameMsg:
if !m.useSpinner {
progressModel, cmd := m.progress.Update(msg)
m.progress = progressModel.(progress.Model)
return m, cmd
}
case spinner.TickMsg:
if m.useSpinner {
spinnerModel, cmd := m.spinner.Update(msg)
m.spinner = spinnerModel
return m, cmd
}
case tea.KeyMsg:
if msg.String() == "q" {
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() string {
if m.done {
return gotext.Get("%s: done!\n", m.filename)
}
if m.useSpinner {
return gotext.Get(
"%s %s downloading at %s/s\n",
m.filename,
m.spinner.View(),
prettyByteSize(int64(m.speed)),
)
}
leftPart := m.filename
rightPart := fmt.Sprintf("%.2f%% (%s/%s, %s/s) [%v:%v]\n", m.percent*100,
prettyByteSize(m.downloaded),
prettyByteSize(m.total),
prettyByteSize(int64(m.speed)),
m.elapsed,
m.remaining,
)
m.progress.Width = m.width - len(leftPart) - len(rightPart) - 6
bar := m.progress.ViewAs(m.percent)
return fmt.Sprintf(
"%s %s %s",
leftPart,
bar,
rightPart,
)
}
func prettyByteSize(b int64) string {
bf := float64(b)
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
if math.Abs(bf) < 1024.0 {
return fmt.Sprintf("%3.1f%sB", bf, unit)
}
bf /= 1024.0
}
return fmt.Sprintf("%.1fYiB", bf)
}
type progressUpdate struct {
percent float64
speed float64
total int64
downloaded int64
elapsed float64
remaining float64
}
type ProgressWriter struct {
baseWriter io.WriteCloser
total int64
downloaded int64
startTime time.Time
onProgress func(progressUpdate)
lastReported time.Time
doneChan chan struct{}
}
func (pw *ProgressWriter) Write(p []byte) (int, error) {
n, err := pw.baseWriter.Write(p)
if err != nil {
return n, err
}
pw.downloaded += int64(n)
now := time.Now()
elapsed := now.Sub(pw.startTime).Seconds()
speed := float64(pw.downloaded) / elapsed
var remaining, percent float64
if pw.total > 0 {
remaining = (float64(pw.total) - float64(pw.downloaded)) / speed
percent = float64(pw.downloaded) / float64(pw.total)
}
if now.Sub(pw.lastReported) > 100*time.Millisecond {
pw.onProgress(progressUpdate{
percent: percent,
speed: speed,
total: pw.total,
downloaded: pw.downloaded,
elapsed: elapsed,
remaining: remaining,
})
pw.lastReported = now
}
return n, nil
}
func (pw *ProgressWriter) Close() error {
pw.onProgress(progressUpdate{
percent: 1,
speed: 0,
downloaded: pw.downloaded,
})
<-pw.doneChan
return nil
}
func NewProgressWriter(base io.WriteCloser, max int64, filename string, out io.Writer) *ProgressWriter {
var m *model
if max == -1 {
m = &model{
spinner: spinner.New(),
useSpinner: true,
filename: filename,
}
m.spinner.Spinner = spinner.Dot
} else {
m = &model{
progress: progress.New(
progress.WithDefaultGradient(),
progress.WithoutPercentage(),
),
useSpinner: false,
filename: filename,
}
}
p := tea.NewProgram(m,
tea.WithInput(nil),
tea.WithOutput(out),
)
pw := &ProgressWriter{
baseWriter: base,
total: max,
startTime: time.Now(),
doneChan: make(chan struct{}),
onProgress: func(update progressUpdate) {
p.Send(update)
},
}
go func() {
defer close(pw.doneChan)
if _, err := p.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error running progress writer: %v\n", err)
os.Exit(1)
}
}()
return pw
}

@ -20,7 +20,6 @@
package dl package dl
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -50,7 +49,7 @@ func (TorrentDownloader) MatchURL(u string) bool {
} }
// Download downloads a file over the BitTorrent protocol. // Download downloads a file over the BitTorrent protocol.
func (TorrentDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { func (TorrentDownloader) Download(opts Options) (Type, string, error) {
aria2Path, err := exec.LookPath("aria2c") aria2Path, err := exec.LookPath("aria2c")
if err != nil { if err != nil {
return 0, "", ErrAria2NotFound return 0, "", ErrAria2NotFound
@ -58,7 +57,7 @@ func (TorrentDownloader) Download(ctx context.Context, opts Options) (Type, stri
opts.URL = strings.TrimPrefix(opts.URL, "torrent+") opts.URL = strings.TrimPrefix(opts.URL, "torrent+")
cmd := exec.CommandContext(ctx, aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL) cmd := exec.Command(aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err = cmd.Run() err = cmd.Run()

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

@ -20,9 +20,7 @@
package overrides package overrides
import ( import (
"fmt"
"reflect" "reflect"
"regexp"
"strings" "strings"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
@ -225,19 +223,3 @@ func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
out = slices.Compact(out) out = slices.Compact(out)
return out, nil return out, nil
} }
func ReleasePlatformSpecific(release int, info *distro.OSRelease) string {
if info.ID == "altlinux" {
return fmt.Sprintf("alt%d", release)
}
if info.ID == "fedora" || slices.Contains(info.Like, "fedora") {
re := regexp.MustCompile(`platform:(\S+)`)
match := re.FindStringSubmatch(info.PlatformID)
if len(match) > 1 {
return fmt.Sprintf("%d.%s", release, match[1])
}
}
return fmt.Sprintf("%d", release)
}

@ -24,7 +24,6 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language" "golang.org/x/text/language"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" "gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
@ -196,42 +195,3 @@ func TestResolveLangs(t *testing.T) {
t.Errorf("expected %v, got %v", expected, names) t.Errorf("expected %v, got %v", expected, names)
} }
} }
func TestReleasePlatformSpecific(t *testing.T) {
type testCase struct {
info *distro.OSRelease
expected string
}
for _, tc := range []testCase{
{
info: &distro.OSRelease{
ID: "centos",
Like: []string{"rhel", "fedora"},
PlatformID: "platform:el8",
},
expected: "1.el8",
},
{
info: &distro.OSRelease{
ID: "fedora",
PlatformID: "platform:f42",
},
expected: "1.f42",
},
{
info: &distro.OSRelease{
ID: "altlinux",
},
expected: "alt1",
},
{
info: &distro.OSRelease{
ID: "ubuntu",
},
expected: "1",
},
} {
assert.Equal(t, tc.expected, overrides.ReleasePlatformSpecific(1, tc.info))
}
}

@ -164,15 +164,26 @@ func (d *Decoder) DecodeVars(val any) error {
return nil return nil
} }
type ( type ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
ScriptFuncWithSubshell func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error)
)
// GetFunc returns a function corresponding to a bash function // GetFunc returns a function corresponding to a bash function
// with the given name // with the given name
func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) { func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
return d.GetFuncP(name, nil) fn := d.getFunc(name)
if fn == nil {
return nil, false
}
return func(ctx context.Context, opts ...interp.RunnerOption) error {
sub := d.Runner.Subshell()
for _, opt := range opts {
err := opt(sub)
if err != nil {
return err
}
}
return sub.Run(ctx, fn)
}, true
} }
type PrepareFunc func(context.Context, *interp.Runner) error type PrepareFunc func(context.Context, *interp.Runner) error
@ -200,24 +211,6 @@ func (d *Decoder) GetFuncP(name string, prepare PrepareFunc) (ScriptFunc, bool)
}, true }, true
} }
func (d *Decoder) GetFuncWithSubshell(name string) (ScriptFuncWithSubshell, bool) {
fn := d.getFunc(name)
if fn == nil {
return nil, false
}
return func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error) {
sub := d.Runner.Subshell()
for _, opt := range opts {
err := opt(sub)
if err != nil {
return nil, err
}
}
return sub, sub.Run(ctx, fn)
}, true
}
func (d *Decoder) getFunc(name string) *syntax.Stmt { func (d *Decoder) getFunc(name string) *syntax.Stmt {
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name)) names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
if err != nil { if err != nil {

@ -29,9 +29,9 @@ import (
"syscall" "syscall"
"time" "time"
"gitea.plemya-x.ru/Plemya-x/fakeroot"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"plemya-x.ru/fakeroot"
) )
// FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go // FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go

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

@ -9,56 +9,40 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: build.go:44 #: build.go:41
msgid "Build a local package" msgid "Build a local package"
msgstr "" msgstr ""
#: build.go:50 #: build.go:47
msgid "Path to the build script" msgid "Path to the build script"
msgstr "" msgstr ""
#: build.go:55 #: build.go:52
msgid "Specify package in script (for multi package script only)"
msgstr ""
#: build.go:60
msgid "Name of the package to build and its repo (example: default/go-bin)" msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "" msgstr ""
#: build.go:65 #: build.go:57
msgid "" msgid ""
"Build package from scratch even if there's an already built package available" "Build package from scratch even if there's an already built package available"
msgstr "" msgstr ""
#: build.go:75 #: build.go:71
msgid "Error db init"
msgstr ""
#: build.go:105
msgid "Package not found"
msgstr ""
#: build.go:123
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "" msgstr ""
#: build.go:131 #: build.go:78
msgid "Unable to detect a supported package manager on the system" msgid "Unable to detect a supported package manager on the system"
msgstr "" msgstr ""
#: build.go:137 #: build.go:89
msgid "Error parsing os release"
msgstr ""
#: build.go:158
msgid "Error building package" msgid "Error building package"
msgstr "" msgstr ""
#: build.go:165 #: build.go:95
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "" msgstr ""
#: build.go:174 #: build.go:103
msgid "Error moving the package" msgid "Error moving the package"
msgstr "" msgstr ""
@ -146,31 +130,31 @@ msgstr ""
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "" msgstr ""
#: install.go:43 #: install.go:42
msgid "Install a new package" msgid "Install a new package"
msgstr "" msgstr ""
#: install.go:57 #: install.go:56
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:124 #: install.go:91
msgid "Error getting packages" msgid "Error getting packages"
msgstr "" msgstr ""
#: install.go:133 #: install.go:100
msgid "Error iterating over packages" msgid "Error iterating over packages"
msgstr "" msgstr ""
#: install.go:146 #: install.go:113
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "" msgstr ""
#: install.go:151 #: install.go:118
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:163 #: install.go:130
msgid "Error removing packages" msgid "Error removing packages"
msgstr "" msgstr ""
@ -198,51 +182,51 @@ msgstr ""
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "" msgstr ""
#: internal/config/config.go:59 #: internal/config/config.go:64
msgid "Error opening config file, using defaults" msgid "Error opening config file, using defaults"
msgstr "" msgstr ""
#: internal/config/config.go:72 #: internal/config/config.go:77
msgid "Error decoding config file, using defaults" msgid "Error decoding config file, using defaults"
msgstr "" msgstr ""
#: internal/config/config.go:84 #: internal/config/config.go:89
msgid "Unable to detect user config directory" msgid "Unable to detect user config directory"
msgstr "" msgstr ""
#: internal/config/config.go:92 #: internal/config/config.go:97
msgid "Unable to create ALR config directory" msgid "Unable to create ALR config directory"
msgstr "" msgstr ""
#: internal/config/config.go:101 #: internal/config/config.go:106
msgid "Unable to create ALR config file" msgid "Unable to create ALR config file"
msgstr "" msgstr ""
#: internal/config/config.go:107 #: internal/config/config.go:112
msgid "Error encoding default configuration" msgid "Error encoding default configuration"
msgstr "" msgstr ""
#: internal/config/config.go:116 #: internal/config/config.go:121
msgid "Unable to detect cache directory" msgid "Unable to detect cache directory"
msgstr "" msgstr ""
#: internal/config/config.go:126 #: internal/config/config.go:131
msgid "Unable to create repo cache directory" msgid "Unable to create repo cache directory"
msgstr "" msgstr ""
#: internal/config/config.go:132 #: internal/config/config.go:137
msgid "Unable to create package cache directory" msgid "Unable to create package cache directory"
msgstr "" msgstr ""
#: internal/config/lang.go:49 #: internal/config/lang.go:50
msgid "Error parsing system language" msgid "Error parsing system language"
msgstr "" msgstr ""
#: internal/db/db.go:133 #: internal/db/db.go:131
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "" msgstr ""
#: internal/db/db.go:140 #: internal/db/db.go:135
msgid "" msgid ""
"Database version does not exist. Run alr fix if something isn't working." "Database version does not exist. Run alr fix if something isn't working."
msgstr "" msgstr ""
@ -267,14 +251,6 @@ msgstr ""
msgid "Downloading source" msgid "Downloading source"
msgstr "" msgstr ""
#: internal/dl/progress_tui.go:100
msgid "%s: done!\n"
msgstr ""
#: internal/dl/progress_tui.go:104
msgid "%s %s downloading at %s/s\n"
msgstr ""
#: internal/logger/log.go:47 #: internal/logger/log.go:47
msgid "ERROR" msgid "ERROR"
msgstr "" msgstr ""
@ -299,80 +275,92 @@ msgstr ""
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "" msgstr ""
#: main.go:91 #: main.go:90
msgid "" msgid ""
"Running ALR as root is forbidden as it may cause catastrophic damage to your " "Running ALR as root is forbidden as it may cause catastrophic damage to your "
"system" "system"
msgstr "" msgstr ""
#: main.go:125 #: main.go:124
msgid "Error while running app" msgid "Error while running app"
msgstr "" msgstr ""
#: pkg/build/build.go:153 #: pkg/build/build.go:104
msgid "Failed to prompt user to view build script" msgid "Failed to prompt user to view build script"
msgstr "" msgstr ""
#: pkg/build/build.go:157 #: pkg/build/build.go:108
msgid "Building package" msgid "Building package"
msgstr "" msgstr ""
#: pkg/build/build.go:228 #: pkg/build/build.go:152
msgid "Downloading sources" msgid "Downloading sources"
msgstr "" msgstr ""
#: pkg/build/build.go:246 #: pkg/build/build.go:164
msgid "Building package metadata" msgid "Building package metadata"
msgstr "" msgstr ""
#: pkg/build/build.go:268 #: pkg/build/build.go:186
msgid "Compressing package" msgid "Compressing package"
msgstr "" msgstr ""
#: pkg/build/build.go:419 #: pkg/build/build.go:312
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:433 #: pkg/build/build.go:323
msgid "This package is already installed" msgid "This package is already installed"
msgstr "" msgstr ""
#: pkg/build/build.go:457 #: pkg/build/build.go:351
msgid "Installing build dependencies" msgid "Installing build dependencies"
msgstr "" msgstr ""
#: pkg/build/build.go:498 #: pkg/build/build.go:393
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "" msgstr ""
#: pkg/build/build.go:533 #: pkg/build/build.go:439
msgid "The checksums array must be the same length as sources" msgid "Executing version()"
msgstr "" msgstr ""
#: pkg/build/build.go:584 #: pkg/build/build.go:459
msgid "Would you like to remove the build dependencies?" msgid "Updating version"
msgstr "" msgstr ""
#: pkg/build/build.go:647 #: pkg/build/build.go:464
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "" msgstr ""
#: pkg/build/build.go:657 #: pkg/build/build.go:474
msgid "Executing build()" msgid "Executing build()"
msgstr "" msgstr ""
#: pkg/build/build.go:687 pkg/build/build.go:707 #: pkg/build/build.go:486
msgid "Executing %s()" msgid "Executing package()"
msgstr "" msgstr ""
#: pkg/build/build.go:766 #: pkg/build/build.go:524
msgid "Error installing native packages" msgid "Executing files()"
msgstr "" msgstr ""
#: pkg/build/build.go:790 #: pkg/build/build.go:601
msgid "Error installing package" msgid "AutoProv is not implemented for this package format, so it's skiped"
msgstr ""
#: pkg/build/build.go:612
msgid "AutoReq is not implemented for this package format, so it's skiped"
msgstr ""
#: pkg/build/build.go:719
msgid "Would you like to remove the build dependencies?"
msgstr ""
#: pkg/build/build.go:825
msgid "The checksums array must be the same length as sources"
msgstr "" msgstr ""
#: pkg/build/findDeps.go:35 #: pkg/build/findDeps.go:35
@ -387,29 +375,29 @@ msgstr ""
msgid "Required dependency found" msgid "Required dependency found"
msgstr "" msgstr ""
#: pkg/build/utils.go:133 #: pkg/build/install.go:42
msgid "AutoProv is not implemented for this package format, so it's skipped" msgid "Error installing native packages"
msgstr "" msgstr ""
#: pkg/build/utils.go:144 #: pkg/build/install.go:79
msgid "AutoReq is not implemented for this package format, so it's skipped" msgid "Error installing package"
msgstr "" msgstr ""
#: pkg/repos/pull.go:79 #: pkg/repos/pull.go:75
msgid "Pulling repository" msgid "Pulling repository"
msgstr "" msgstr ""
#: pkg/repos/pull.go:103 #: pkg/repos/pull.go:99
msgid "Repository up to date" msgid "Repository up to date"
msgstr "" msgstr ""
#: pkg/repos/pull.go:160 #: pkg/repos/pull.go:156
msgid "Git repository does not appear to be a valid ALR repo" msgid "Git repository does not appear to be a valid ALR repo"
msgstr "" msgstr ""
#: pkg/repos/pull.go:176 #: pkg/repos/pull.go:172
msgid "" msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try " "ALR repo's minumum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work." "updating ALR if something doesn't work."
msgstr "" msgstr ""
@ -457,54 +445,14 @@ msgstr ""
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "" msgstr ""
#: search.go:36 #: upgrade.go:46
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:94
msgid "At least one search parameter is required"
msgstr ""
#: search.go:100
msgid "Error db search"
msgstr ""
#: search.go:109
msgid "Error parsing format template"
msgstr ""
#: search.go:124
msgid "Error executing template"
msgstr ""
#: upgrade.go:47
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "" msgstr ""
#: upgrade.go:90 #: upgrade.go:82
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "" msgstr ""
#: upgrade.go:112 #: upgrade.go:93
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "" msgstr ""

@ -1,12 +1,11 @@
# #
# 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-01-24 21:20+0300\n" "PO-Revision-Date: 2025-01-22 16:50+0300\n"
"Last-Translator: x1z53 <x1z53@yandex.ru>\n" "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
"Language-Team: Russian\n" "Language-Team: Russian\n"
"Language: ru\n" "Language: ru\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -16,171 +15,156 @@ msgstr ""
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 47.1\n" "X-Generator: Gtranslator 47.1\n"
#: build.go:44 #: build.go:41
#, fuzzy
msgid "Build a local package" msgid "Build a local package"
msgstr "Сборка локального пакета" msgstr "Сборка пакета"
#: build.go:50 #: build.go:47
#, fuzzy
msgid "Path to the build script" msgid "Path to the build script"
msgstr "Путь к скрипту сборки" msgstr "Не удалось предложить просмотреть скрипт"
#: build.go:55 #: build.go:52
msgid "Specify package in script (for multi package script only)" msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "" msgstr ""
#: build.go:60 #: build.go:57
msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)"
#: build.go:65
msgid "" msgid ""
"Build package from scratch even if there's an already built package available" "Build package from scratch even if there's an already built package available"
msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет"
#: build.go:75
msgid "Error db init"
msgstr "" msgstr ""
#: build.go:105 #: build.go:71
msgid "Package not found"
msgstr ""
#: build.go:123
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев" msgstr ""
#: build.go:131 #: build.go:78
msgid "Unable to detect a supported package manager on the system" msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" msgstr ""
#: build.go:137 #: build.go:89
#, fuzzy
msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: build.go:158
msgid "Error building package" msgid "Error building package"
msgstr "Ошибка при сборке пакета" msgstr "Ошибка при сборке пакета"
#: build.go:165 #: build.go:95
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога" msgstr ""
#: build.go:174 #: build.go:103
msgid "Error moving the package" msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета" msgstr ""
#: fix.go:37 #: fix.go:37
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR" msgstr ""
#: fix.go:44 #: fix.go:44
msgid "Removing cache directory" msgid "Removing cache directory"
msgstr "Удаление каталога кэша" msgstr ""
#: fix.go:48 #: fix.go:48
msgid "Unable to remove cache directory" msgid "Unable to remove cache directory"
msgstr "Не удалось удалить каталог кэша" msgstr ""
#: fix.go:52 #: fix.go:52
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "Восстановление кэша" msgstr ""
#: fix.go:56 #: fix.go:56
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша" msgstr ""
#: fix.go:62 #: fix.go:62
msgid "Error pulling repos" msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев" msgstr ""
#: fix.go:66 #: fix.go:66
msgid "Done" msgid "Done"
msgstr "Сделано" msgstr ""
#: gen.go:34 #: gen.go:34
msgid "Generate a ALR script from a template" msgid "Generate a ALR script from a template"
msgstr "Генерация скрипта ALR из шаблона" msgstr ""
#: gen.go:39 #: gen.go:39
msgid "Generate a ALR script for a pip module" msgid "Generate a ALR script for a pip module"
msgstr "Генерация скрипта ALR для модуля pip" msgstr ""
#: helper.go:41 #: helper.go:41
msgid "List all the available helper commands" msgid "List all the available helper commands"
msgstr "Список всех доступных вспомогательных команды" msgstr ""
#: helper.go:53 #: helper.go:53
msgid "Run a ALR helper command" msgid "Run a ALR helper command"
msgstr "Запустить вспомогательную команду ALR" msgstr ""
#: helper.go:60 #: helper.go:60
msgid "The directory that the install commands will install to" msgid "The directory that the install commands will install to"
msgstr "Каталог, в который будут устанавливать команды установки" msgstr ""
#: helper.go:73 #: helper.go:73
msgid "No such helper command" msgid "No such helper command"
msgstr "Такой вспомогательной команды нет" msgstr ""
#: info.go:42 #: info.go:42
msgid "Print information about a package" msgid "Print information about a package"
msgstr "Отобразить информацию о пакете" msgstr "Напечатать информацию о пакете"
#: info.go:47 #: 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:57 #: info.go:57
msgid "Error initialization database" msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных" msgstr ""
#: info.go:64 #: info.go:64
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.go:78 #: info.go:78
msgid "Error finding packages" msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов" msgstr ""
#: info.go:94 #: info.go:94
msgid "Error parsing os-release file" msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы" msgstr ""
#: info.go:103 #: info.go:103
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений" msgstr ""
#: info.go:112 info.go:118 #: info.go:112 info.go:118
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита" msgstr ""
#: install.go:43 #: install.go:42
msgid "Install a new package" msgid "Install a new package"
msgstr "Установить новый пакет" msgstr ""
#: install.go:57 #: install.go:56
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" msgstr ""
#: install.go:124 #: install.go:91
msgid "Error getting packages" msgid "Error getting packages"
msgstr "Ошибка при получении пакетов" msgstr ""
#: install.go:133 #: install.go:100
msgid "Error iterating over packages" msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов" msgstr ""
#: install.go:146 #: install.go:113
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "Удалить установленный пакет" msgstr ""
#: install.go:151 #: install.go:118
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" msgstr ""
#: install.go:163 #: install.go:130
msgid "Error removing packages" msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов" msgstr ""
#: internal/cliutils/prompt.go:60 #: internal/cliutils/prompt.go:60
msgid "Would you like to view the build script for %s" msgid "Would you like to view the build script for %s"
@ -196,349 +180,288 @@ msgstr "Пользователь решил не продолжать после
#: internal/cliutils/prompt.go:111 #: internal/cliutils/prompt.go:111
msgid "Error prompting for choice of package" msgid "Error prompting for choice of package"
msgstr "Ошибка при запросе выбора пакета" msgstr ""
#: internal/cliutils/prompt.go:135 #: internal/cliutils/prompt.go:135
msgid "Choose which package to %s" msgid "Choose which package to %s"
msgstr "Выберите, какой пакет использовать для %s" msgstr ""
#: internal/cliutils/prompt.go:156 #: internal/cliutils/prompt.go:156
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "Выберите, какой дополнительный пакет(ы) следует установить" msgstr ""
#: internal/config/config.go:59 #: internal/config/config.go:64
msgid "Error opening config file, using defaults" msgid "Error opening config file, using defaults"
msgstr "" msgstr ""
"Ошибка при открытии конфигурационного файла, используются значения по "
"умолчанию"
#: internal/config/config.go:72 #: internal/config/config.go:77
msgid "Error decoding config file, using defaults" msgid "Error decoding config file, using defaults"
msgstr "" msgstr ""
"Ошибка при декодировании конфигурационного файла, используются значения по "
"умолчанию"
#: internal/config/config.go:84 #: internal/config/config.go:89
msgid "Unable to detect user config directory" msgid "Unable to detect user config directory"
msgstr "Не удалось обнаружить каталог конфигурации пользователя" msgstr ""
#: internal/config/config.go:92 #: internal/config/config.go:97
msgid "Unable to create ALR config directory" msgid "Unable to create ALR config directory"
msgstr "Не удалось создать каталог конфигурации ALR" msgstr ""
#: internal/config/config.go:101 #: internal/config/config.go:106
msgid "Unable to create ALR config file" msgid "Unable to create ALR config file"
msgstr "Не удалось создать конфигурационный файл ALR" msgstr ""
#: internal/config/config.go:107 #: internal/config/config.go:112
msgid "Error encoding default configuration" msgid "Error encoding default configuration"
msgstr "Ошибка кодирования конфигурации по умолчанию" msgstr ""
#: internal/config/config.go:116 #: internal/config/config.go:121
msgid "Unable to detect cache directory" msgid "Unable to detect cache directory"
msgstr "Не удалось обнаружить каталог кэша" msgstr ""
#: internal/config/config.go:126 #: internal/config/config.go:131
msgid "Unable to create repo cache directory" msgid "Unable to create repo cache directory"
msgstr "Не удалось создать каталог кэша репозитория" msgstr ""
#: internal/config/config.go:132 #: internal/config/config.go:137
msgid "Unable to create package cache directory" msgid "Unable to create package cache directory"
msgstr "Не удалось создать каталог кэша пакетов" msgstr ""
#: internal/config/lang.go:49 #: internal/config/lang.go:50
msgid "Error parsing system language" msgid "Error parsing system language"
msgstr "Ошибка при парсинге языка системы" msgstr ""
#: internal/db/db.go:133 #: internal/db/db.go:131
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек" msgstr ""
#: internal/db/db.go:140 #: internal/db/db.go:135
msgid "" msgid ""
"Database version does not exist. Run alr fix if something isn't working." "Database version does not exist. Run alr fix if something isn't working."
msgstr "" msgstr ""
"Версия базы данных не существует. Запустите alr fix, если что-то не работает."
#: internal/db/db_legacy.go:101 #: internal/db/db_legacy.go:101
msgid "Error opening database" msgid "Error opening database"
msgstr "Ошибка при открытии базы данных" 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 ""
#: internal/dl/dl.go:201 #: internal/dl/dl.go:201
msgid "Source found in cache and linked to destination" msgid "Source found in cache and linked to destination"
msgstr "Источник найден в кэше и связан с пунктом назначения" msgstr ""
#: internal/dl/dl.go:208 #: internal/dl/dl.go:208
msgid "Source updated and linked to destination" msgid "Source updated and linked to destination"
msgstr "Источник обновлён и связан с пунктом назначения" msgstr ""
#: internal/dl/dl.go:222 #: internal/dl/dl.go:222
msgid "Downloading source" msgid "Downloading source"
msgstr "Скачивание источника" msgstr "Скачивание источника"
#: internal/dl/progress_tui.go:100
msgid "%s: done!\n"
msgstr ""
#: internal/dl/progress_tui.go:104
msgid "%s %s downloading at %s/s\n"
msgstr ""
#: internal/logger/log.go:47 #: internal/logger/log.go:47
msgid "ERROR" msgid "ERROR"
msgstr "ОШИБКА" msgstr "ОШИБКА"
#: list.go:40 #: list.go:40
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "Список пакетов репозитория ALR" msgstr ""
#: list.go:91 #: list.go:91
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов" msgstr ""
#: main.go:45 #: main.go:45
msgid "Print the current ALR version and exit" msgid "Print the current ALR version and exit"
msgstr "Показать текущую версию ALR и выйти" msgstr ""
#: main.go:61 #: main.go:61
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "Аргументы, которые будут переданы менеджеру пакетов" msgstr ""
#: main.go:67 #: main.go:67
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов" msgstr ""
#: main.go:91 #: main.go:90
msgid "" msgid ""
"Running ALR as root is forbidden as it may cause catastrophic damage to your " "Running ALR as root is forbidden as it may cause catastrophic damage to your "
"system" "system"
msgstr "" msgstr ""
"Запуск ALR от имени root запрещён, так как это может привести к "
"катастрофическому повреждению вашей системы"
#: main.go:125 #: main.go:124
msgid "Error while running app" msgid "Error while running app"
msgstr "Ошибка при запуске приложения" msgstr ""
#: pkg/build/build.go:153 #: pkg/build/build.go:104
msgid "Failed to prompt user to view build script" msgid "Failed to prompt user to view build script"
msgstr "Не удалось предложить пользователю просмотреть скрипт сборки" msgstr "Не удалось предложить просмотреть скрипт"
#: pkg/build/build.go:157 #: pkg/build/build.go:108
msgid "Building package" msgid "Building package"
msgstr "Сборка пакета" msgstr "Сборка пакета"
#: pkg/build/build.go:228 #: pkg/build/build.go:152
msgid "Downloading sources" msgid "Downloading sources"
msgstr "Скачивание источников" msgstr "Скачивание файлов"
#: pkg/build/build.go:246 #: pkg/build/build.go:164
msgid "Building package metadata" msgid "Building package metadata"
msgstr "Сборка метаданных пакета" msgstr ""
#: pkg/build/build.go:268 #: pkg/build/build.go:186
msgid "Compressing package" msgid "Compressing package"
msgstr "Сжатие пакета" msgstr ""
#: pkg/build/build.go:419 #: pkg/build/build.go:312
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:433 #: pkg/build/build.go:323
msgid "This package is already installed" msgid "This package is already installed"
msgstr "Этот пакет уже установлен" msgstr ""
#: pkg/build/build.go:457 #: pkg/build/build.go:351
msgid "Installing build dependencies" msgid "Installing build dependencies"
msgstr "Установка зависимостей сборки" msgstr ""
#: pkg/build/build.go:498 #: pkg/build/build.go:393
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "Установка зависимостей" msgstr ""
#: pkg/build/build.go:533 #: pkg/build/build.go:439
msgid "The checksums array must be the same length as sources" msgid "Executing version()"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники" msgstr ""
#: pkg/build/build.go:584 #: pkg/build/build.go:459
msgid "Would you like to remove the build dependencies?" msgid "Updating version"
msgstr "Хотели бы вы удалить зависимости сборки?" msgstr ""
#: pkg/build/build.go:647 #: pkg/build/build.go:464
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "Исполнение prepare()" msgstr ""
#: pkg/build/build.go:657 #: pkg/build/build.go:474
msgid "Executing build()" msgid "Executing build()"
msgstr "Исполнение build()" msgstr ""
#: pkg/build/build.go:687 pkg/build/build.go:707 #: pkg/build/build.go:486
#, fuzzy msgid "Executing package()"
msgid "Executing %s()" msgstr ""
msgstr "Исполнение files()"
#: pkg/build/build.go:766 #: pkg/build/build.go:524
msgid "Error installing native packages" msgid "Executing files()"
msgstr "Ошибка при установке нативных пакетов" msgstr ""
#: pkg/build/build.go:790 #: pkg/build/build.go:601
msgid "Error installing package" msgid "AutoProv is not implemented for this package format, so it's skiped"
msgstr "Ошибка при установке пакета" msgstr ""
#: pkg/build/build.go:612
msgid "AutoReq is not implemented for this package format, so it's skiped"
msgstr ""
#: pkg/build/build.go:719
msgid "Would you like to remove the build dependencies?"
msgstr ""
#: pkg/build/build.go:825
msgid "The checksums array must be the same length as sources"
msgstr ""
#: pkg/build/findDeps.go:35 #: pkg/build/findDeps.go:35
msgid "Command not found on the system" msgid "Command not found on the system"
msgstr "Команда не найдена в системе" msgstr ""
#: pkg/build/findDeps.go:82 #: pkg/build/findDeps.go:82
msgid "Provided dependency found" msgid "Provided dependency found"
msgstr "Найденная предоставленная зависимость" msgstr ""
#: pkg/build/findDeps.go:89 #: pkg/build/findDeps.go:89
msgid "Required dependency found" msgid "Required dependency found"
msgstr "Найдена требуемая зависимость"
#: pkg/build/utils.go:133
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr "" msgstr ""
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/utils.go:144 #: pkg/build/install.go:42
msgid "AutoReq is not implemented for this package format, so it's skipped" msgid "Error installing native packages"
msgstr "" msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/repos/pull.go:79 #: pkg/build/install.go:79
msgid "Error installing package"
msgstr ""
#: pkg/repos/pull.go:75
msgid "Pulling repository" msgid "Pulling repository"
msgstr "Скачивание репозитория" msgstr "Скачивание репозитория"
#: pkg/repos/pull.go:103 #: pkg/repos/pull.go:99
msgid "Repository up to date" msgid "Repository up to date"
msgstr "Репозиторий уже обновлён" msgstr "Репозиторий уже обновлен"
#: pkg/repos/pull.go:160 #: pkg/repos/pull.go:156
msgid "Git repository does not appear to be a valid ALR repo" msgid "Git repository does not appear to be a valid ALR repo"
msgstr "Репозиторий Git не поддерживается репозиторием ALR" msgstr ""
#: pkg/repos/pull.go:176 #: pkg/repos/pull.go:172
msgid "" msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try " "ALR repo's minumum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work." "updating ALR if something doesn't work."
msgstr "" msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает."
#: repo.go:41 #: repo.go:41
#, fuzzy
msgid "Add a new repository" msgid "Add a new repository"
msgstr "Добавить новый репозиторий" msgstr "Скачивание репозитория"
#: repo.go:48 #: repo.go:48
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "Название нового репозитория" msgstr ""
#: repo.go:54 #: repo.go:54
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория" msgstr ""
#: repo.go:79 repo.go:136 #: repo.go:79 repo.go:136
msgid "Error opening config file" msgid "Error opening config file"
msgstr "Ошибка при открытии конфигурационного файла" msgstr ""
#: repo.go:85 repo.go:142 #: repo.go:85 repo.go:142
msgid "Error encoding config" msgid "Error encoding config"
msgstr "Ошибка при кодировании конфигурации" msgstr ""
#: repo.go:103 #: repo.go:103
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий" msgstr ""
#: repo.go:110 #: repo.go:110
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён" msgstr ""
#: repo.go:128 #: repo.go:128
msgid "Repo does not exist" msgid "Repo does not exist"
msgstr "Репозитория не существует" msgstr ""
#: repo.go:148 #: repo.go:148
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория" msgstr ""
#: repo.go:154 #: repo.go:154
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных" msgstr ""
#: repo.go:166 #: repo.go:166
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории"
#: search.go:36
msgid "Search packages"
msgstr "" msgstr ""
#: search.go:42 #: upgrade.go:46
msgid "Search by name"
msgstr ""
#: search.go:47
msgid "Search by description"
msgstr ""
#: search.go:52
#, fuzzy
msgid "Search by repository"
msgstr "Добавить новый репозиторий"
#: search.go:57
msgid "Search by provides"
msgstr ""
#: search.go:62
msgid "Format output using a Go template"
msgstr ""
#: search.go:94
msgid "At least one search parameter is required"
msgstr ""
#: search.go:100
msgid "Error db search"
msgstr ""
#: search.go:109
#, fuzzy
msgid "Error parsing format template"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: search.go:124
#, fuzzy
msgid "Error executing template"
msgstr "Ошибка при получении пакетов"
#: upgrade.go:47
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты" msgstr ""
#: upgrade.go:90 #: upgrade.go:82
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr ""
#: upgrade.go:112 #: upgrade.go:93
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "Здесь нечего делать." msgstr ""
#~ msgid "Executing version()"
#~ msgstr "Исполнение версия()"
#~ msgid "Updating version"
#~ msgstr "Обновление версии"
#~ msgid "Executing package()"
#~ msgstr "Исполнение package()"

@ -23,61 +23,11 @@ import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
type BuildOpts struct { type BuildOpts struct {
Script string Script string
Packages []string
Manager manager.Manager Manager manager.Manager
Clean bool Clean bool
Interactive bool Interactive bool
} }
type BuildVarsPre struct {
Version string `sh:"version,required"`
Release int `sh:"release,required"`
Epoch uint `sh:"epoch"`
Description string `sh:"desc"`
Homepage string `sh:"homepage"`
Maintainer string `sh:"maintainer"`
Architectures []string `sh:"architectures"`
Licenses []string `sh:"license"`
Provides []string `sh:"provides"`
Conflicts []string `sh:"conflicts"`
Depends []string `sh:"deps"`
BuildDepends []string `sh:"build_deps"`
OptDepends []string `sh:"opt_deps"`
Replaces []string `sh:"replaces"`
Sources []string `sh:"sources"`
Checksums []string `sh:"checksums"`
Backup []string `sh:"backup"`
Scripts Scripts `sh:"scripts"`
AutoReq []string `sh:"auto_req"`
AutoProv []string `sh:"auto_prov"`
}
func (bv *BuildVarsPre) ToBuildVars() BuildVars {
return BuildVars{
Name: "",
Version: bv.Version,
Release: bv.Release,
Epoch: bv.Epoch,
Description: bv.Description,
Homepage: bv.Homepage,
Maintainer: bv.Maintainer,
Architectures: bv.Architectures,
Licenses: bv.Licenses,
Provides: bv.Provides,
Conflicts: bv.Conflicts,
Depends: bv.Depends,
BuildDepends: bv.BuildDepends,
OptDepends: bv.OptDepends,
Replaces: bv.Replaces,
Sources: bv.Sources,
Checksums: bv.Checksums,
Backup: bv.Backup,
Scripts: bv.Scripts,
AutoReq: bv.AutoReq,
AutoProv: bv.AutoProv,
}
}
// BuildVars represents the script variables required // BuildVars represents the script variables required
// to build a package // to build a package
type BuildVars struct { type BuildVars struct {

@ -81,7 +81,6 @@ func GetApp() *cli.App {
GenCmd(), GenCmd(),
HelperCmd(), HelperCmd(),
VersionCmd(), VersionCmd(),
SearchCmd(),
}, },
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context

File diff suppressed because it is too large Load Diff

@ -18,18 +18,12 @@ package build
import ( import (
"context" "context"
"fmt"
"os"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
) )
@ -142,145 +136,90 @@ func (m *TestManager) IsInstalled(pkg string) (bool, error) {
return true, nil return true, nil
} }
type TestConfig struct{} func TestInstallBuildDeps(t *testing.T) {
type testEnv struct {
pf PackageFinder
vars *types.BuildVars
opts types.BuildOpts
func (c *TestConfig) PagerStyle(ctx context.Context) string { // Contains pkgs captured by FindPkgsFunc
return "native" capturedPkgs []string
}
func (c *TestConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{
CacheDir: "/tmp",
}
}
func TestExecuteFirstPassIsSecure(t *testing.T) {
cfg := &TestConfig{}
pf := &TestPackageFinder{}
info := &distro.OSRelease{}
m := &TestManager{}
opts := types.BuildOpts{
Manager: m,
Interactive: false,
} }
ctx := context.Background()
b := NewBuilder(
ctx,
opts,
pf,
info,
cfg,
)
tmpFile, err := os.CreateTemp("", "testfile-")
assert.NoError(t, err)
tmpFilePath := tmpFile.Name()
defer os.Remove(tmpFilePath)
_, err = os.Stat(tmpFilePath)
assert.NoError(t, err)
testScript := fmt.Sprintf(`name='test'
version=1.0.0
release=1
rm -f %s`, tmpFilePath)
fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "alr.sh")
assert.NoError(t, err)
_, _, err = b.executeFirstPass(fl)
assert.NoError(t, err)
_, err = os.Stat(tmpFilePath)
assert.NoError(t, err)
}
func TestExecuteFirstPassIsCorrect(t *testing.T) {
type testCase struct { type testCase struct {
Name string Name string
Script string Prepare func() *testEnv
Opts types.BuildOpts Expected func(t *testing.T, e *testEnv, res []string, err error)
Expected func(t *testing.T, vars []*types.BuildVars)
} }
for _, tc := range []testCase{{ for _, tc := range []testCase{
Name: "single package", {
Script: `name='test' Name: "install only needed deps",
version='1.0.0' Prepare: func() *testEnv {
release=1 pf := TestPackageFinder{}
epoch=2 vars := types.BuildVars{}
desc="Test package" m := TestManager{}
homepage='https://example.com' opts := types.BuildOpts{
maintainer='Ivan Ivanov' Manager: &m,
`, Interactive: false,
Opts: types.BuildOpts{ }
Manager: &TestManager{},
Interactive: false, env := &testEnv{
pf: &pf,
vars: &vars,
opts: opts,
capturedPkgs: []string{},
}
pf.FindPkgsFunc = func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
env.capturedPkgs = append(env.capturedPkgs, pkgs...)
result := make(map[string][]db.Package)
result["bar"] = []db.Package{{
Name: "bar-pkg",
}}
result["buz"] = []db.Package{{
Name: "buz-pkg",
}}
return result, []string{}, nil
}
vars.BuildDepends = []string{
"foo",
"bar",
"buz",
}
m.IsInstalledFunc = func(pkg string) (bool, error) {
if pkg == "foo" {
return true, nil
} else {
return false, nil
}
}
return env
},
Expected: func(t *testing.T, e *testEnv, res []string, err error) {
assert.NoError(t, err)
assert.Len(t, res, 2)
assert.ElementsMatch(t, res, []string{"bar-pkg", "buz-pkg"})
assert.ElementsMatch(t, e.capturedPkgs, []string{"bar", "buz"})
},
}, },
Expected: func(t *testing.T, vars []*types.BuildVars) { } {
assert.Equal(t, 1, len(vars)) t.Run(tc.Name, func(tt *testing.T) {
assert.Equal(t, vars[0].Name, "test")
assert.Equal(t, vars[0].Version, "1.0.0")
assert.Equal(t, vars[0].Release, int(1))
assert.Equal(t, vars[0].Epoch, uint(2))
assert.Equal(t, vars[0].Description, "Test package")
},
}, {
Name: "multiple packages",
Script: `name=(
foo
bar
)
version='0.0.1'
release=1
epoch=2
desc="Test package"
meta_foo() {
desc="Foo package"
}
meta_bar() {
}
`,
Opts: types.BuildOpts{
Packages: []string{"foo"},
Manager: &TestManager{},
Interactive: false,
},
Expected: func(t *testing.T, vars []*types.BuildVars) {
assert.Equal(t, 1, len(vars))
assert.Equal(t, vars[0].Name, "foo")
assert.Equal(t, vars[0].Description, "Foo package")
},
}} {
t.Run(tc.Name, func(t *testing.T) {
cfg := &TestConfig{}
pf := &TestPackageFinder{}
info := &distro.OSRelease{}
ctx := context.Background() ctx := context.Background()
env := tc.Prepare()
b := NewBuilder( result, err := installBuildDeps(
ctx, ctx,
tc.Opts, env.pf,
pf, env.vars,
info, env.opts,
cfg,
) )
fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh") tc.Expected(tt, env, result, err)
assert.NoError(t, err)
_, allVars, err := b.executeFirstPass(fl)
assert.NoError(t, err)
tc.Expected(t, allVars)
}) })
} }
} }

84
pkg/build/install.go Normal file

@ -0,0 +1,84 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"log/slog"
"os"
"path/filepath"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
// InstallPkgs устанавливает нативные пакеты с использованием менеджера пакетов,
// затем строит и устанавливает пакеты ALR
func InstallPkgs(ctx context.Context, alrPkgs []db.Package, nativePkgs []string, opts types.BuildOpts) {
if len(nativePkgs) > 0 {
err := opts.Manager.Install(nil, nativePkgs...)
// Если есть нативные пакеты, выполняем их установку
if err != nil {
slog.Error(gotext.Get("Error installing native packages"), "err", err)
os.Exit(1)
// Логируем и завершаем выполнение при ошибке
}
}
InstallScripts(ctx, GetScriptPaths(ctx, alrPkgs), opts)
// Устанавливаем скрипты сборки через функцию InstallScripts
}
// GetScriptPaths возвращает срез путей к скриптам, соответствующий
// данным пакетам
func GetScriptPaths(ctx context.Context, pkgs []db.Package) []string {
var scripts []string
for _, pkg := range pkgs {
// Для каждого пакета создаем путь к скрипту сборки
scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, pkg.Repository, pkg.Name, "alr.sh")
scripts = append(scripts, scriptPath)
}
return scripts
}
// InstallScripts строит и устанавливает переданные alr скрипты сборки
func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) {
for _, script := range scripts {
opts.Script = script // Устанавливаем текущий скрипт в опции
builtPkgs, _, err := BuildPackage(ctx, opts)
// Выполняем сборку пакета
if err != nil {
slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1)
// Логируем и завершаем выполнение при ошибке сборки
}
err = opts.Manager.InstallLocal(nil, builtPkgs...)
// Устанавливаем локально собранные пакеты
if err != nil {
slog.Error(gotext.Get("Error installing package"), "err", err)
os.Exit(1)
// Логируем и завершаем выполнение при ошибке установки
}
}
}

@ -1,423 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"io"
"log/slog"
"os"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
// Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH).
_ "github.com/goreleaser/nfpm/v2/apk"
_ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm"
"github.com/leonelquinteros/gotext"
"mvdan.cc/sh/v3/syntax"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
// Функция readScript анализирует скрипт сборки с использованием встроенной реализации bash
func readScript(script string) (*syntax.File, error) {
fl, err := os.Open(script) // Открываем файл скрипта
if err != nil {
return nil, err
}
defer fl.Close() // Закрываем файл после выполнения
file, err := syntax.NewParser().Parse(fl, "alr.sh") // Парсим скрипт с помощью синтаксического анализатора
if err != nil {
return nil, err
}
return file, nil // Возвращаем синтаксическое дерево
}
// Функция prepareDirs подготавливает директории для сборки.
func prepareDirs(dirs types.Directories) error {
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
if err != nil {
return err
}
err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников
if err != nil {
return err
}
return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов
}
// Функция buildPkgMetadata создает метаданные для пакета, который будет собран.
func buildPkgMetadata(
ctx context.Context,
vars *types.BuildVars,
dirs types.Directories,
pkgFormat string,
info *distro.OSRelease,
deps []string,
preferedContents *[]string,
) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars, info)
pkgInfo.Description = vars.Description
pkgInfo.Platform = "linux"
pkgInfo.Homepage = vars.Homepage
pkgInfo.License = strings.Join(vars.Licenses, ", ")
pkgInfo.Maintainer = vars.Maintainer
pkgInfo.Overridables = nfpm.Overridables{
Conflicts: vars.Conflicts,
Replaces: vars.Replaces,
Provides: vars.Provides,
Depends: deps,
}
if pkgFormat == "apk" {
// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы
pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
return s == pkgInfo.Name
})
}
if vars.Epoch != 0 {
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
}
setScripts(vars, pkgInfo, dirs.ScriptDir)
if slices.Contains(vars.Architectures, "all") {
pkgInfo.Arch = "all"
}
contents, err := buildContents(vars, dirs, preferedContents)
if err != nil {
return nil, err
}
pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) {
if pkgFormat == "rpm" {
err = rpmFindProvides(ctx, pkgInfo, dirs)
if err != nil {
return nil, err
}
} else {
slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped"))
}
}
if len(vars.AutoReq) == 1 && decoder.IsTruthy(vars.AutoReq[0]) {
if pkgFormat == "rpm" {
err = rpmFindRequires(ctx, pkgInfo, dirs)
if err != nil {
return nil, err
}
} else {
slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped"))
}
}
return pkgInfo, nil
}
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
// которые будут включены в конечный пакет.
func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
contents := []*files.Content{}
processPath := func(path, trimmed string, prefered bool) error {
fi, err := os.Lstat(path)
if err != nil {
return err
}
if fi.IsDir() {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
if !prefered {
_, err = f.Readdirnames(1)
if err != io.EOF {
return nil
}
}
contents = append(contents, &files.Content{
Source: path,
Destination: trimmed,
Type: "dir",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
},
})
return nil
}
if fi.Mode()&os.ModeSymlink != 0 {
link, err := os.Readlink(path)
if err != nil {
return err
}
link = strings.TrimPrefix(link, dirs.PkgDir)
contents = append(contents, &files.Content{
Source: link,
Destination: trimmed,
Type: "symlink",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
},
})
return nil
}
fileContent := &files.Content{
Source: path,
Destination: trimmed,
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
Size: fi.Size(),
},
}
if slices.Contains(vars.Backup, trimmed) {
fileContent.Type = "config|noreplace"
}
contents = append(contents, fileContent)
return nil
}
if preferedContents != nil {
for _, trimmed := range *preferedContents {
path := filepath.Join(dirs.PkgDir, trimmed)
if err := processPath(path, trimmed, true); err != nil {
return nil, err
}
}
} else {
err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
trimmed := strings.TrimPrefix(path, dirs.PkgDir)
return processPath(path, trimmed, false)
})
if err != nil {
return nil, err
}
}
return contents, nil
}
// Функция checkForBuiltPackage пытается обнаружить ранее собранный пакет и вернуть его путь
// и true, если нашла. Если нет, возвратит "", false, nil.
func checkForBuiltPackage(
mgr manager.Manager,
vars *types.BuildVars,
pkgFormat,
baseDir string,
info *distro.OSRelease,
) (string, bool, error) {
filename, err := pkgFileName(vars, pkgFormat, info)
if err != nil {
return "", false, err
}
pkgPath := filepath.Join(baseDir, filename)
_, err = os.Stat(pkgPath)
if err != nil {
return "", false, nil
}
return pkgPath, true, nil
}
func getBasePkgInfo(vars *types.BuildVars, info *distro.OSRelease) *nfpm.Info {
return &nfpm.Info{
Name: vars.Name,
Arch: cpu.Arch(),
Version: vars.Version,
Release: overrides.ReleasePlatformSpecific(vars.Release, info),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
}
}
// pkgFileName returns the filename of the package if it were to be built.
// This is used to check if the package has already been built.
func pkgFileName(vars *types.BuildVars, pkgFormat string, info *distro.OSRelease) (string, error) {
pkgInfo := getBasePkgInfo(vars, info)
packager, err := nfpm.Get(pkgFormat)
if err != nil {
return "", err
}
return packager.ConventionalFileName(pkgInfo), nil
}
// Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// или ALR_PKG_FORMAT, если он установлен.
func getPkgFormat(mgr manager.Manager) string {
pkgFormat := mgr.Format()
if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok {
pkgFormat = format
}
return pkgFormat
}
// Функция createBuildEnvVars создает переменные окружения, которые будут установлены
// в скрипте сборки при его выполнении.
func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string {
env := os.Environ()
env = append(
env,
"DISTRO_NAME="+info.Name,
"DISTRO_PRETTY_NAME="+info.PrettyName,
"DISTRO_ID="+info.ID,
"DISTRO_VERSION_ID="+info.VersionID,
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
"ARCH="+cpu.Arch(),
"NCPU="+strconv.Itoa(runtime.NumCPU()),
)
if dirs.ScriptDir != "" {
env = append(env, "scriptdir="+dirs.ScriptDir)
}
if dirs.PkgDir != "" {
env = append(env, "pkgdir="+dirs.PkgDir)
}
if dirs.SrcDir != "" {
env = append(env, "srcdir="+dirs.SrcDir)
}
return env
}
// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) {
if vars.Scripts.PreInstall != "" {
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
}
if vars.Scripts.PostInstall != "" {
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall)
}
if vars.Scripts.PreRemove != "" {
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove)
}
if vars.Scripts.PostRemove != "" {
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove)
}
if vars.Scripts.PreUpgrade != "" {
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
}
if vars.Scripts.PostUpgrade != "" {
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
}
if vars.Scripts.PreTrans != "" {
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans)
}
if vars.Scripts.PostTrans != "" {
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans)
}
}
/*
// Функция setVersion изменяет переменную версии в скрипте runner.
// Она используется для установки версии на вывод функции version().
func setVersion(ctx context.Context, r *interp.Runner, to string) error {
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
if err != nil {
return err
}
return r.Run(ctx, fl)
}
*/
// Returns not installed dependencies
func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) {
filteredPackages := []string{}
for _, dep := range dependencies {
installed, err := opts.Manager.IsInstalled(dep)
if err != nil {
return nil, err
}
if installed {
continue
}
filteredPackages = append(filteredPackages, dep)
}
return filteredPackages, nil
}
// Функция packageNames возвращает имена всех предоставленных пакетов.
func packageNames(pkgs []db.Package) []string {
names := make([]string, len(pkgs))
for i, p := range pkgs {
names[i] = p.Name
}
return names
}
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates(slice []string) []string {
seen := map[string]struct{}{}
result := []string{}
for _, s := range slice {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
result = append(result, s)
}
}
return result
}

@ -44,7 +44,6 @@ type OSRelease struct {
SupportURL string SupportURL string
BugReportURL string BugReportURL string
Logo string Logo string
PlatformID string
} }
var parsed *OSRelease var parsed *OSRelease
@ -103,7 +102,6 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
SupportURL: runner.Vars["SUPPORT_URL"].Str, SupportURL: runner.Vars["SUPPORT_URL"].Str,
BugReportURL: runner.Vars["BUG_REPORT_URL"].Str, BugReportURL: runner.Vars["BUG_REPORT_URL"].Str,
Logo: runner.Vars["LOGO"].Str, Logo: runner.Vars["LOGO"].Str,
PlatformID: runner.Vars["PLATFORM_ID"].Str,
} }
distroUpdated := false distroUpdated := false

@ -22,8 +22,6 @@ package repos
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io"
"log/slog" "log/slog"
"net/url" "net/url"
"os" "os"
@ -43,10 +41,8 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
type actionType uint8 type actionType uint8
@ -173,7 +169,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
// to compare it to the repo version, so only compare versions with the "v". // to compare it to the repo version, so only compare versions with the "v".
if strings.HasPrefix(config.Version, "v") { if strings.HasPrefix(config.Version, "v") {
if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 { if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 {
slog.Warn(gotext.Get("ALR repo's minimum ALR version is greater than the current version. Try updating ALR if something doesn't work."), "repo", repo.Name) slog.Warn(gotext.Get("ALR repo's minumum ALR version is greater than the current version. Try updating ALR if something doesn't work."), "repo", repo.Name)
} }
} }
} }
@ -181,96 +177,6 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
return nil return nil
} }
func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.Runner, scriptFl io.ReadCloser) error {
parser := syntax.NewParser()
defer scriptFl.Close()
fl, err := parser.Parse(scriptFl, "alr.sh")
if err != nil {
return err
}
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return err
}
type packages struct {
BasePkgName string `sh:"basepkg_name"`
Names []string `sh:"name"`
}
var pkgs packages
d := decoder.New(&distro.OSRelease{}, runner)
d.Overrides = false
d.LikeDistros = false
err = d.DecodeVars(&pkgs)
if err != nil {
return err
}
if len(pkgs.Names) > 1 {
if pkgs.BasePkgName == "" {
pkgs.BasePkgName = pkgs.Names[0]
}
for _, pkgName := range pkgs.Names {
pkgInfo := PackageInfo{}
funcName := fmt.Sprintf("meta_%s", pkgName)
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return err
}
meta, ok := d.GetFuncWithSubshell(funcName)
if !ok {
return errors.New("func is missing")
}
r, err := meta(ctx)
if err != nil {
return err
}
d := decoder.New(&distro.OSRelease{}, r)
d.Overrides = false
d.LikeDistros = false
err = d.DecodeVars(&pkgInfo)
if err != nil {
return err
}
pkg := pkgInfo.ToPackage(repo.Name)
resolveOverrides(r, pkg)
pkg.Name = pkgName
pkg.BasePkgName = pkgs.BasePkgName
err = rs.db.InsertPackage(ctx, *pkg)
if err != nil {
return err
}
}
return nil
}
pkg := EmptyPackage(repo.Name)
err = d.DecodeVars(pkg)
if err != nil {
return err
}
resolveOverrides(runner, pkg)
return rs.db.InsertPackage(ctx, *pkg)
}
func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Runner, error) {
env := append(os.Environ(), "scriptdir="+scriptDir)
return interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
)
}
func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error { func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error {
oldCommit, err := r.CommitObject(old.Hash()) oldCommit, err := r.CommitObject(old.Hash())
if err != nil { if err != nil {
@ -295,33 +201,34 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
continue continue
} }
switch { if to == nil {
case to == nil:
actions = append(actions, action{ actions = append(actions, action{
Type: actionDelete, Type: actionDelete,
File: from.Path(), File: from.Path(),
}) })
case from == nil: } else if from == nil {
actions = append(actions, action{ actions = append(actions, action{
Type: actionUpdate, Type: actionUpdate,
File: to.Path(), File: to.Path(),
}) })
case from.Path() != to.Path(): } else {
actions = append(actions, if from.Path() != to.Path() {
action{ actions = append(actions,
Type: actionDelete, action{
File: from.Path(), Type: actionDelete,
}, File: from.Path(),
action{ },
action{
Type: actionUpdate,
File: to.Path(),
},
)
} else {
actions = append(actions, action{
Type: actionUpdate, Type: actionUpdate,
File: to.Path(), File: to.Path(),
}, })
) }
default:
actions = append(actions, action{
Type: actionUpdate,
File: to.Path(),
})
} }
} }
@ -329,7 +236,15 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
parser := syntax.NewParser() parser := syntax.NewParser()
for _, action := range actions { for _, action := range actions {
runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(filepath.Join(repoDir, action.File))) env := append(os.Environ(), "scriptdir="+filepath.Dir(filepath.Join(repoDir, action.File)))
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
)
if err != nil { if err != nil {
return err return err
} }
@ -375,7 +290,23 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
return nil return nil
} }
err = rs.updatePkg(ctx, repo, runner, r) pkg := db.Package{
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repo.Name,
}
err = parseScript(ctx, parser, runner, r, &pkg)
if err != nil {
return err
}
resolveOverrides(runner, &pkg)
err = rs.db.InsertPackage(ctx, pkg)
if err != nil { if err != nil {
return err return err
} }
@ -392,8 +323,18 @@ func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir s
return err return err
} }
parser := syntax.NewParser()
for _, match := range matches { for _, match := range matches {
runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) env := append(os.Environ(), "scriptdir="+filepath.Dir(match))
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
)
if err != nil { if err != nil {
return err return err
} }
@ -403,7 +344,23 @@ func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir s
return err return err
} }
err = rs.updatePkg(ctx, repo, runner, scriptFl) pkg := db.Package{
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repo.Name,
}
err = parseScript(ctx, parser, runner, scriptFl, &pkg)
if err != nil {
return err
}
resolveOverrides(runner, &pkg)
err = rs.db.InsertPackage(ctx, pkg)
if err != nil { if err != nil {
return err return err
} }

@ -1,173 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repos
import (
"context"
"io"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{
DBPath: ":memory:",
}
}
func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo {
return []types.Repo{
{
Name: "test",
URL: "https://test",
},
}
}
func createReadCloserFromString(input string) io.ReadCloser {
reader := strings.NewReader(input)
return struct {
io.Reader
io.Closer
}{
Reader: reader,
Closer: io.NopCloser(reader),
}
}
func TestUpdatePkg(t *testing.T) {
type testCase struct {
name string
file string
verify func(context.Context, *db.Database)
}
repo := types.Repo{
Name: "test",
URL: "https://test",
}
for _, tc := range []testCase{
{
name: "single package",
file: `name=foo
version='0.0.1'
release=1
desc="main desc"
deps=('sudo')
build_deps=('golang')
`,
verify: func(ctx context.Context, database *db.Database) {
result, err := database.GetPkgs(ctx, "1 = 1")
assert.NoError(t, err)
pkgCount := 0
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
assert.Equal(t, "foo", dbPkg.Name)
assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
pkgCount++
}
assert.Equal(t, 1, pkgCount)
},
},
{
name: "multiple package",
file: `basepkg_name=foo
name=(
bar
buz
)
version='0.0.1'
release=1
desc="main desc"
deps=('sudo')
build_deps=('golang')
meta_bar() {
desc="foo desc"
}
meta_buz() {
deps+=('doas')
}
`,
verify: func(ctx context.Context, database *db.Database) {
result, err := database.GetPkgs(ctx, "1 = 1")
assert.NoError(t, err)
pkgCount := 0
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
if dbPkg.Name == "bar" {
assert.Equal(t, db.NewJSON(map[string]string{"": "foo desc"}), dbPkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
}
if dbPkg.Name == "buz" {
assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo", "doas"}}), dbPkg.Depends)
}
pkgCount++
}
assert.Equal(t, 2, pkgCount)
},
},
} {
t.Run(tc.name, func(t *testing.T) {
cfg := &TestALRConfig{}
ctx := context.Background()
database := db.New(&TestALRConfig{})
database.Init(ctx)
rs := New(cfg, database)
path, err := os.MkdirTemp("", "test-update-pkg")
assert.NoError(t, err)
defer os.RemoveAll(path)
runner, err := rs.processRepoChangesRunner(path, path)
assert.NoError(t, err)
err = rs.updatePkg(ctx, repo, runner, createReadCloserFromString(
tc.file,
))
assert.NoError(t, err)
tc.verify(ctx, database)
})
}
}

@ -21,6 +21,7 @@ import (
"sync" "sync"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
@ -35,6 +36,15 @@ func Pull(ctx context.Context, repos []types.Repo) error {
return GetInstance(ctx).Pull(ctx, repos) return GetInstance(ctx).Pull(ctx, repos)
} }
// FindPkgs looks for packages matching the inputs inside the database.
// It returns a map that maps the package name input to any packages found for it.
// It also returns a slice that contains the names of all packages that were not found.
//
// Deprecated: use struct method
func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
return GetInstance(ctx).FindPkgs(ctx, pkgs)
}
// ======================= // =======================
// FOR LEGACY ONLY // FOR LEGACY ONLY
// ======================= // =======================

@ -67,47 +67,6 @@ func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runn
return d.DecodeVars(pkg) return d.DecodeVars(pkg)
} }
type PackageInfo struct {
Version string `sh:"version,required"`
Release int `sh:"release,required"`
Epoch uint `sh:"epoch"`
Architectures db.JSON[[]string] `sh:"architectures"`
Licenses db.JSON[[]string] `sh:"license"`
Provides db.JSON[[]string] `sh:"provides"`
Conflicts db.JSON[[]string] `sh:"conflicts"`
Replaces db.JSON[[]string] `sh:"replaces"`
}
func (inf *PackageInfo) ToPackage(repoName string) *db.Package {
return &db.Package{
Version: inf.Version,
Release: inf.Release,
Epoch: inf.Epoch,
Architectures: inf.Architectures,
Licenses: inf.Licenses,
Provides: inf.Provides,
Conflicts: inf.Conflicts,
Replaces: inf.Replaces,
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repoName,
}
}
func EmptyPackage(repoName string) *db.Package {
return &db.Package{
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repoName,
}
}
var overridable = map[string]string{ var overridable = map[string]string{
"deps": "Depends", "deps": "Depends",
"build_deps": "BuildDepends", "build_deps": "BuildDepends",

@ -1,46 +0,0 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#!/bin/bash
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
COLOR="#4c1"
if (( $(echo "$COVERAGE < 50" | bc -l) )); then
COLOR="#e05d44"
elif (( $(echo "$COVERAGE < 80" | bc -l) )); then
COLOR="#dfb317"
fi
cat <<EOF > coverage-badge.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="109" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="109" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)"><rect width="65" height="20" fill="#555"/>
<rect x="65" width="44" height="20" fill="${COLOR}"/>
<rect width="109" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">${COVERAGE}%</text>
<text x="86" y="14">${COVERAGE}%</text>
</g>
</svg>
EOF

@ -39,7 +39,7 @@ installPkg() {
else else
warn "Не обнаружена команда повышения привилегий (например, sudo, doas)" warn "Не обнаружена команда повышения привилегий (например, sudo, doas)"
fi fi
case $1 in case $1 in
pacman) $rootCmd pacman --noconfirm -U ${@:2} ;; pacman) $rootCmd pacman --noconfirm -U ${@:2} ;;
apk) $rootCmd apk add --allow-untrusted ${@:2} ;; apk) $rootCmd apk add --allow-untrusted ${@:2} ;;
@ -78,10 +78,6 @@ elif command -v apk &>/dev/null; then
info "Обнаружен apk" info "Обнаружен apk"
pkgFormat="apk" pkgFormat="apk"
pkgMgr="apk" pkgMgr="apk"
elif command -v apt-get &>/dev/null; then
info "Обнаружен apt-get"
pkgFormat="rpm"
pkgMgr="apt-get"
else else
warn "Не обнаружен поддерживаемый менеджер пакетов!" warn "Не обнаружен поддерживаемый менеджер пакетов!"
noPkgMgr=true noPkgMgr=true
@ -102,10 +98,7 @@ if [ -z "$noPkgMgr" ]; then
elif [ "$pkgMgr" == "apt" ]; then elif [ "$pkgMgr" == "apt" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.amd64.deb' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.amd64.deb' | sort -V | tail -n 1)
elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt1' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.x86_64.rpm' | sort -V | tail -n 1)
elif [[ "$pkgMgr" == "apt-get" ]]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*alt1.x86_64.rpm' | sort -V | tail -n 1)
else else
error "Не поддерживаемый менеджер пакетов для автоматической установки" error "Не поддерживаемый менеджер пакетов для автоматической установки"
fi fi

136
search.go

@ -1,136 +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"
"strings"
"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"
)
func SearchCmd() *cli.Command {
return &cli.Command{
Name: "search",
Usage: gotext.Get("Search packages"),
Aliases: []string{"s"},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Usage: gotext.Get("Search by name"),
},
&cli.StringFlag{
Name: "description",
Aliases: []string{"d"},
Usage: gotext.Get("Search by description"),
},
&cli.StringFlag{
Name: "repository",
Aliases: []string{"repo"},
Usage: gotext.Get("Search by repository"),
},
&cli.StringFlag{
Name: "provides",
Aliases: []string{"p"},
Usage: gotext.Get("Search by provides"),
},
&cli.StringFlag{
Name: "format",
Aliases: []string{"f"},
Usage: gotext.Get("Format output using a Go template"),
},
},
Action: func(c *cli.Context) error {
ctx := c.Context
cfg := config.New()
db := database.New(cfg)
err := db.Init(ctx)
defer db.Close()
if err != nil {
slog.Error(gotext.Get("Error db init"), "err", err)
os.Exit(1)
}
var conditions []string
if name := c.String("name"); name != "" {
conditions = append(conditions, fmt.Sprintf("name LIKE '%%%s%%'", name))
}
if description := c.String("description"); description != "" {
conditions = append(conditions, fmt.Sprintf("description LIKE '%%%s%%'", description))
}
if repo := c.String("repository"); repo != "" {
conditions = append(conditions, fmt.Sprintf("repository = '%s'", repo))
}
if provides := c.String("provides"); provides != "" {
conditions = append(conditions, fmt.Sprintf("EXISTS (SELECT 1 FROM json_each(provides) WHERE value = '%s')", provides))
}
query := ""
if len(conditions) > 0 {
query = strings.Join(conditions, " AND ")
} else {
slog.Error(gotext.Get("At least one search parameter is required"))
os.Exit(1)
}
result, err := db.GetPkgs(ctx, query)
if err != nil {
slog.Error(gotext.Get("Error db search"), "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)
}
}
for result.Next() {
var dbPkg database.Package
err = result.StructScan(&dbPkg)
if err != nil {
os.Exit(1)
}
if tmpl != nil {
err = tmpl.Execute(os.Stdout, dbPkg)
if err != nil {
slog.Error(gotext.Get("Error executing template"), "err", err)
os.Exit(1)
}
fmt.Println()
} else {
fmt.Println(dbPkg.Name)
}
}
return nil
},
}
}

@ -32,8 +32,7 @@ import (
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
@ -56,14 +55,7 @@ func UpgradeCmd() *cli.Command {
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
cfg := config.New() cfg := config.GetInstance(ctx)
db := database.New(cfg)
rs := repos.New(cfg, db)
err := db.Init(ctx)
if err != nil {
slog.Error(gotext.Get("Error db init"), "err", err)
os.Exit(1)
}
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
@ -78,32 +70,21 @@ func UpgradeCmd() *cli.Command {
} }
if cfg.AutoPull(ctx) { if cfg.AutoPull(ctx) {
err = rs.Pull(ctx, cfg.Repos(ctx)) err = repos.Pull(ctx, config.Config(ctx).Repos)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err) slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1) os.Exit(1)
} }
} }
updates, err := checkForUpdates(ctx, mgr, cfg, rs, info) updates, err := checkForUpdates(ctx, mgr, info)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error checking for updates"), "err", err) slog.Error(gotext.Get("Error checking for updates"), "err", err)
os.Exit(1) os.Exit(1)
} }
if len(updates) > 0 { if len(updates) > 0 {
builder := build.NewBuilder( build.InstallPkgs(ctx, updates, nil, types.BuildOpts{
ctx,
types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
rs,
info,
cfg,
)
builder.InstallPkgs(ctx, updates, nil, types.BuildOpts{
Manager: mgr, Manager: mgr,
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
@ -117,33 +98,27 @@ func UpgradeCmd() *cli.Command {
} }
} }
func checkForUpdates( func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) {
ctx context.Context,
mgr manager.Manager,
cfg *config.ALRConfig,
rs *repos.Repos,
info *distro.OSRelease,
) ([]database.Package, error) {
installed, err := mgr.ListInstalled(nil) installed, err := mgr.ListInstalled(nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pkgNames := maps.Keys(installed) pkgNames := maps.Keys(installed)
found, _, err := rs.FindPkgs(ctx, pkgNames) found, _, err := repos.FindPkgs(ctx, pkgNames)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var out []database.Package var out []db.Package
for pkgName, pkgs := range found { for pkgName, pkgs := range found {
if slices.Contains(cfg.IgnorePkgUpdates(ctx), pkgName) { if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkgName) {
continue continue
} }
if len(pkgs) > 1 { if len(pkgs) > 1 {
// Puts the element with the highest version first // Puts the element with the highest version first
slices.SortFunc(pkgs, func(a, b database.Package) int { slices.SortFunc(pkgs, func(a, b db.Package) int {
return vercmp.Compare(a.Version, b.Version) return vercmp.Compare(a.Version, b.Version)
}) })
} }
@ -152,12 +127,10 @@ func checkForUpdates(
pkg := pkgs[0] pkg := pkgs[0]
repoVer := pkg.Version repoVer := pkg.Version
releaseStr := overrides.ReleasePlatformSpecific(pkg.Release, info)
if pkg.Release != 0 && pkg.Epoch == 0 { if pkg.Release != 0 && pkg.Epoch == 0 {
repoVer = fmt.Sprintf("%s-%s", pkg.Version, releaseStr) repoVer = fmt.Sprintf("%s-%d", pkg.Version, pkg.Release)
} else if pkg.Release != 0 && pkg.Epoch != 0 { } else if pkg.Release != 0 && pkg.Epoch != 0 {
repoVer = fmt.Sprintf("%d:%s-%s", pkg.Epoch, pkg.Version, releaseStr) repoVer = fmt.Sprintf("%d:%s-%d", pkg.Epoch, pkg.Version, pkg.Release)
} }
c := vercmp.Compare(repoVer, installed[pkgName]) c := vercmp.Compare(repoVer, installed[pkgName])