Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d97210635 | |||
| 51f05ecc81 | |||
| 27082ba223 | |||
| 6c6a7152b4 | |||
| 681e31df4e | |||
| a84a9be782 | |||
| 825e89d0d3 | |||
| eb88fbd123 | |||
| 3c5613199e | |||
| 73bdb54496 | |||
| 4b5fd855cc | |||
| 2c8eb6be48 | |||
| d4396756d6 | |||
| c8be92c47b | |||
| ee7f4878d1 | |||
| bd769468a9 | |||
| 09d9053104 | |||
| d914391c40 | |||
| 0ece536810 | |||
| 192e3e8a4f | |||
| fa4604c26c | |||
| 163ad12575 | |||
| 2ba6136c99 | |||
| 438304f8ce | |||
| 657b562f31 | |||
| d26b288cde | |||
| e785c6b53d | |||
| ff8ed902ea | |||
| 1d2d46cbc5 | |||
| 374c206fae | |||
| d906dc8d86 | |||
| 2f81f7c605 | |||
| bb05a8d38b | |||
| 076f90bbd7 | |||
| e772ecf2ab | |||
| 3e0c110893 | |||
| 238f4cf682 | |||
| c41e7e1c18 | |||
| 0144ad17d9 | |||
| ddd9d1d63d | |||
| 0cb23911cf | |||
| de8399e40b | |||
| d226a6c154 | |||
| c2e0332552 |
@@ -20,6 +20,8 @@ archives:
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
arm64: aarch64
|
||||
files:
|
||||
- scripts/completion/*
|
||||
nfpms:
|
||||
- id: lure
|
||||
package_name: linux-user-repository
|
||||
@@ -38,27 +40,27 @@ nfpms:
|
||||
- rpm
|
||||
- archlinux
|
||||
provides:
|
||||
- lure
|
||||
- linux-user-repository
|
||||
conflicts:
|
||||
- lure
|
||||
- linux-user-repository
|
||||
contents:
|
||||
- src: scripts/completion/bash
|
||||
dst: /usr/share/bash-completion/completions/lure
|
||||
- src: scripts/completion/zsh
|
||||
dst: /usr/share/zsh/site-functions/_lure
|
||||
aurs:
|
||||
- name: lure-bin
|
||||
- name: linux-user-repository-bin
|
||||
homepage: 'https://gitea.arsenm.dev/Arsen6331/lure'
|
||||
description: "Linux User REpository"
|
||||
maintainers:
|
||||
- 'Arsen Musayelyan <arsen@arsenm.dev>'
|
||||
license: GPLv3
|
||||
private_key: '{{ .Env.AUR_KEY }}'
|
||||
git_url: 'ssh://aur@aur.archlinux.org/lure-bin.git'
|
||||
git_url: 'ssh://aur@aur.archlinux.org/linux-user-repository-bin.git'
|
||||
provides:
|
||||
- lure
|
||||
- linux-user-repository
|
||||
conflicts:
|
||||
- lure
|
||||
- linux-user-repository
|
||||
depends:
|
||||
- sudo
|
||||
- pacman
|
||||
@@ -67,8 +69,8 @@ aurs:
|
||||
install -Dm755 ./lure "${pkgdir}/usr/bin/lure"
|
||||
|
||||
# completions
|
||||
install -Dm755 ./scripts/completion/bash /usr/share/bash-completion/completions/lure
|
||||
install -Dm755 ./scripts/completion/zsh /usr/share/zsh/site-functions/_lure
|
||||
install -Dm755 ./scripts/completion/bash ${pkgdir}/usr/share/bash-completion/completions/lure
|
||||
install -Dm755 ./scripts/completion/zsh ${pkgdir}/usr/share/zsh/site-functions/_lure
|
||||
release:
|
||||
gitea:
|
||||
owner: Arsen6331
|
||||
|
||||
6
Makefile
6
Makefile
@@ -1,6 +1,6 @@
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
lure: version.txt
|
||||
lure: internal/config/version.txt
|
||||
go build
|
||||
|
||||
clean:
|
||||
@@ -16,7 +16,7 @@ installmisc:
|
||||
uninstall:
|
||||
rm -f /usr/local/bin/lure
|
||||
|
||||
version.txt:
|
||||
go generate ./...
|
||||
internal/config/version.txt:
|
||||
go generate ./internal/config
|
||||
|
||||
.PHONY: install clean uninstall
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
[](https://goreportcard.com/report/go.arsenm.dev/lure)
|
||||
[](https://ci.arsenm.dev/Arsen6331/lure)
|
||||
[](https://aur.archlinux.org/packages/lure-bin/)
|
||||
[](https://aur.archlinux.org/packages/linux-user-repository-bin/)
|
||||
|
||||
LURE is a distro-agnostic build system for Linux, similar to the [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). It is currently in an ***alpha*** state and may not be stable. It is currently able to successfully and consistently build and install packages on various distributions, but there are some bugs that still need to be ironed out.
|
||||
LURE is a distro-agnostic build system for Linux, similar to the [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). It is currently in **beta**. Most major bugs have been fixed, and most major features have been added. LURE is ready for general use, but may still break or change occasionally.
|
||||
|
||||
LURE is written in pure Go and has zero dependencies after building. The only things LURE requires are a command for privilege elevation such as `sudo`, `doas`, etc. as well as a supported package manager. Currently, LURE supports `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. If a supported package manager exists on your system, it will be detected and used automatically.
|
||||
|
||||
|
||||
159
build.go
159
build.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -40,10 +41,11 @@ import (
|
||||
"github.com/goreleaser/nfpm/v2/files"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/download"
|
||||
"go.arsenm.dev/lure/internal/cliutils"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
"go.arsenm.dev/lure/internal/cpu"
|
||||
"go.arsenm.dev/lure/internal/db"
|
||||
"go.arsenm.dev/lure/internal/dl"
|
||||
"go.arsenm.dev/lure/internal/repos"
|
||||
"go.arsenm.dev/lure/internal/shutils"
|
||||
"go.arsenm.dev/lure/internal/shutils/decoder"
|
||||
@@ -100,7 +102,7 @@ func buildCmd(c *cli.Context) error {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
|
||||
pkgPaths, _, err := buildPackage(c.Context, script, mgr)
|
||||
pkgPaths, _, err := buildPackage(c.Context, script, mgr, c.Bool("clean"))
|
||||
if err != nil {
|
||||
log.Fatal("Error building package").Err(err).Send()
|
||||
}
|
||||
@@ -123,7 +125,7 @@ func buildCmd(c *cli.Context) error {
|
||||
|
||||
// buildPackage builds the script at the given path. It returns two slices. One contains the paths
|
||||
// to the built package(s), the other contains the names of the built package(s).
|
||||
func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]string, []string, error) {
|
||||
func buildPackage(ctx context.Context, script string, mgr manager.Manager, clean bool) ([]string, []string, error) {
|
||||
info, err := distro.ParseOSRelease(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -187,13 +189,28 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = cliutils.PromptViewScript(script, vars.Name, cfg.PagerStyle)
|
||||
baseDir := filepath.Join(config.PkgsDir, vars.Name)
|
||||
srcdir := filepath.Join(baseDir, "src")
|
||||
pkgdir := filepath.Join(baseDir, "pkg")
|
||||
|
||||
if !clean {
|
||||
builtPkgPath, ok, err := checkForBuiltPackage(mgr, &vars, getPkgFormat(mgr), baseDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
return []string{builtPkgPath}, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = cliutils.PromptViewScript(script, vars.Name, cfg.PagerStyle, translator)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to prompt user to view build script").Err(err).Send()
|
||||
}
|
||||
|
||||
if !archMatches(vars.Architectures) {
|
||||
buildAnyway, err := cliutils.YesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", true)
|
||||
buildAnyway, err := cliutils.YesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", true, translator)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -230,10 +247,6 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
dec.LikeDistros = false
|
||||
}
|
||||
|
||||
baseDir := filepath.Join(config.PkgsDir, vars.Name)
|
||||
srcdir := filepath.Join(baseDir, "src")
|
||||
pkgdir := filepath.Join(baseDir, "pkg")
|
||||
|
||||
err = os.RemoveAll(baseDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -254,21 +267,27 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var buildDeps []string
|
||||
for _, pkgName := range vars.BuildDepends {
|
||||
if _, ok := installed[pkgName]; !ok {
|
||||
buildDeps = append(buildDeps, pkgName)
|
||||
}
|
||||
if instVer, ok := installed[vars.Name]; ok {
|
||||
log.Warn("This package is already installed").
|
||||
Str("name", vars.Name).
|
||||
Str("version", instVer).
|
||||
Send()
|
||||
}
|
||||
|
||||
if len(buildDeps) > 0 {
|
||||
found, notFound, err := repos.FindPkgs(gdb, buildDeps)
|
||||
var buildDeps []string
|
||||
if len(vars.BuildDepends) > 0 {
|
||||
found, notFound, err := repos.FindPkgs(gdb, vars.BuildDepends)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
found = filterBuildDeps(found, installed)
|
||||
|
||||
log.Info("Installing build dependencies").Send()
|
||||
installPkgs(ctx, cliutils.FlattenPkgs(found, "install"), notFound, mgr)
|
||||
|
||||
flattened := cliutils.FlattenPkgs(found, "install", translator)
|
||||
buildDeps = packageNames(flattened)
|
||||
installPkgs(ctx, flattened, notFound, mgr, clean)
|
||||
}
|
||||
|
||||
var builtDeps, builtNames, repoDeps []string
|
||||
@@ -280,9 +299,9 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
scripts := getScriptPaths(cliutils.FlattenPkgs(found, "install"))
|
||||
scripts := getScriptPaths(cliutils.FlattenPkgs(found, "install", translator))
|
||||
for _, script := range scripts {
|
||||
pkgPaths, pkgNames, err := buildPackage(ctx, script, mgr)
|
||||
pkgPaths, pkgNames, err := buildPackage(ctx, script, mgr, clean)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -361,6 +380,8 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
} else {
|
||||
log.Fatal("The package() function is required").Send()
|
||||
}
|
||||
|
||||
log.Info("Building package metadata").Str("name", vars.Name).Send()
|
||||
|
||||
uniq(
|
||||
&repoDeps,
|
||||
@@ -464,12 +485,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
|
||||
pkgInfo.Overridables.Contents = contents
|
||||
|
||||
pkgFormat := mgr.Format()
|
||||
if format, ok := os.LookupEnv("LURE_PKG_FORMAT"); ok {
|
||||
pkgFormat = format
|
||||
}
|
||||
|
||||
packager, err := nfpm.Get(pkgFormat)
|
||||
packager, err := nfpm.Get(getPkgFormat(mgr))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -484,6 +500,8 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Info("Compressing package").Str("name", pkgName).Send()
|
||||
|
||||
err = packager.Package(pkgInfo, pkgFile)
|
||||
if err != nil {
|
||||
@@ -491,7 +509,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
}
|
||||
|
||||
if len(buildDeps) > 0 {
|
||||
removeBuildDeps, err := cliutils.YesNoPrompt("Would you like to remove build dependencies?", false)
|
||||
removeBuildDeps, err := cliutils.YesNoPrompt("Would you like to remove build dependencies?", false, translator)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -515,6 +533,47 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
return pkgPaths, pkgNames, nil
|
||||
}
|
||||
|
||||
func checkForBuiltPackage(mgr manager.Manager, vars *BuildVars, pkgFormat, baseDir string) (string, bool, error) {
|
||||
filename, err := pkgFileName(vars, pkgFormat)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
pkgPath := filepath.Join(baseDir, filename)
|
||||
|
||||
_, err = os.Stat(pkgPath)
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
return pkgPath, true, nil
|
||||
}
|
||||
|
||||
func pkgFileName(vars *BuildVars, pkgFormat string) (string, error) {
|
||||
pkgInfo := &nfpm.Info{
|
||||
Name: vars.Name,
|
||||
Arch: cpu.Arch(),
|
||||
Version: vars.Version,
|
||||
Release: strconv.Itoa(vars.Release),
|
||||
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
|
||||
}
|
||||
|
||||
packager, err := nfpm.Get(pkgFormat)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return packager.ConventionalFileName(pkgInfo), nil
|
||||
}
|
||||
|
||||
func getPkgFormat(mgr manager.Manager) string {
|
||||
pkgFormat := mgr.Format()
|
||||
if format, ok := os.LookupEnv("LURE_PKG_FORMAT"); ok {
|
||||
pkgFormat = format
|
||||
}
|
||||
return pkgFormat
|
||||
}
|
||||
|
||||
func genBuildEnv(info *distro.OSRelease, scriptdir string) []string {
|
||||
env := os.Environ()
|
||||
|
||||
@@ -541,10 +600,11 @@ func getSources(ctx context.Context, srcdir string, bv *BuildVars) error {
|
||||
}
|
||||
|
||||
for i, src := range bv.Sources {
|
||||
opts := download.GetOptions{
|
||||
SourceURL: src,
|
||||
opts := dl.Options{
|
||||
Name: fmt.Sprintf("%s[%d]", bv.Name, i),
|
||||
URL: src,
|
||||
Destination: srcdir,
|
||||
EncloseGit: true,
|
||||
Progress: os.Stderr,
|
||||
}
|
||||
|
||||
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
|
||||
@@ -552,10 +612,10 @@ func getSources(ctx context.Context, srcdir string, bv *BuildVars) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.SHA256Sum = checksum
|
||||
opts.SHA256 = checksum
|
||||
}
|
||||
|
||||
err := download.Get(ctx, opts)
|
||||
err := dl.Download(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -633,6 +693,41 @@ func setVersion(ctx context.Context, r *interp.Runner, to string) error {
|
||||
return r.Run(ctx, fl)
|
||||
}
|
||||
|
||||
func filterBuildDeps(found map[string][]db.Package, installed map[string]string) map[string][]db.Package {
|
||||
out := map[string][]db.Package{}
|
||||
for name, pkgs := range found {
|
||||
var inner []db.Package
|
||||
for _, pkg := range pkgs {
|
||||
if _, ok := installed[pkg.Name]; !ok {
|
||||
addToFiltered := true
|
||||
for _, provides := range pkg.Provides.Val {
|
||||
if _, ok := installed[provides]; ok {
|
||||
addToFiltered = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if addToFiltered {
|
||||
inner = append(inner, pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(inner) > 0 {
|
||||
out[name] = inner
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func packageNames(pkgs []db.Package) []string {
|
||||
names := make([]string, len(pkgs))
|
||||
for i, p := range pkgs {
|
||||
names[i] = p.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// uniq removes all duplicates from string slices
|
||||
func uniq(ss ...*[]string) {
|
||||
for _, s := range ss {
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
@@ -9,9 +27,11 @@ import (
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/twitchtv/twirp"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/lure/internal/api"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
"go.arsenm.dev/lure/internal/db"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type lureWebAPI struct {
|
||||
@@ -59,7 +79,7 @@ func (l lureWebAPI) Search(ctx context.Context, req *api.SearchRequest) (*api.Se
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Packages = append(out.Packages, dbPkgToAPI(pkg))
|
||||
out.Packages = append(out.Packages, dbPkgToAPI(ctx, pkg))
|
||||
}
|
||||
|
||||
return out, err
|
||||
@@ -70,7 +90,7 @@ func (l lureWebAPI) GetPkg(ctx context.Context, req *api.GetPackageRequest) (*ap
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dbPkgToAPI(pkg), nil
|
||||
return dbPkgToAPI(ctx, pkg), nil
|
||||
}
|
||||
|
||||
func (l lureWebAPI) GetBuildScript(ctx context.Context, req *api.GetBuildScriptRequest) (*api.GetBuildScriptResponse, error) {
|
||||
@@ -94,16 +114,16 @@ func (l lureWebAPI) GetBuildScript(ctx context.Context, req *api.GetBuildScriptR
|
||||
return &api.GetBuildScriptResponse{Script: string(data)}, nil
|
||||
}
|
||||
|
||||
func dbPkgToAPI(pkg *db.Package) *api.Package {
|
||||
func dbPkgToAPI(ctx context.Context, pkg *db.Package) *api.Package {
|
||||
return &api.Package{
|
||||
Name: pkg.Name,
|
||||
Repository: pkg.Repository,
|
||||
Version: pkg.Version,
|
||||
Release: int64(pkg.Release),
|
||||
Epoch: ptr(int64(pkg.Epoch)),
|
||||
Description: &pkg.Description,
|
||||
Homepage: &pkg.Homepage,
|
||||
Maintainer: &pkg.Maintainer,
|
||||
Description: performTranslation(ctx, pkg.Description.Val),
|
||||
Homepage: performTranslation(ctx, pkg.Homepage.Val),
|
||||
Maintainer: performTranslation(ctx, pkg.Maintainer.Val),
|
||||
Architectures: pkg.Architectures.Val,
|
||||
Licenses: pkg.Licenses.Val,
|
||||
Provides: pkg.Provides.Val,
|
||||
@@ -125,3 +145,61 @@ func dbMapToAPI(m map[string][]string) map[string]*api.StringList {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func performTranslation(ctx context.Context, v map[string]string) *string {
|
||||
alVal := ctx.Value(acceptLanguageKey{})
|
||||
langVal := ctx.Value(langParameterKey{})
|
||||
|
||||
if alVal == nil && langVal == nil {
|
||||
val, ok := v[""]
|
||||
if !ok {
|
||||
return ptr("<unknown>")
|
||||
}
|
||||
return &val
|
||||
}
|
||||
|
||||
al, _ := alVal.(string)
|
||||
lang, _ := langVal.(string)
|
||||
|
||||
tags, _, err := language.ParseAcceptLanguage(al)
|
||||
if err != nil {
|
||||
log.Warn("Error parsing Accept-Language header").Err(err).Send()
|
||||
}
|
||||
|
||||
var bases []string
|
||||
if lang != "" {
|
||||
langTag, err := language.Parse(lang)
|
||||
if err != nil {
|
||||
log.Warn("Error parsing lang parameter").Err(err).Send()
|
||||
bases = getLangBases(tags)
|
||||
} else {
|
||||
bases = getLangBases(append([]language.Tag{langTag}, tags...))
|
||||
}
|
||||
} else {
|
||||
bases = getLangBases(tags)
|
||||
}
|
||||
|
||||
if len(bases) == 1 {
|
||||
bases = []string{"en", ""}
|
||||
}
|
||||
|
||||
for _, name := range bases {
|
||||
val, ok := v[name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
return &val
|
||||
}
|
||||
|
||||
return ptr("<unknown>")
|
||||
}
|
||||
|
||||
func getLangBases(langs []language.Tag) []string {
|
||||
out := make([]string, len(langs)+1)
|
||||
for i, lang := range langs {
|
||||
base, _ := lang.Base()
|
||||
out[i] = base.String()
|
||||
}
|
||||
out[len(out)-1] = ""
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
"os"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
@@ -12,21 +28,9 @@ import (
|
||||
var gdb *sqlx.DB
|
||||
|
||||
func init() {
|
||||
fi, err := os.Stat(config.DBPath)
|
||||
if err == nil {
|
||||
// TODO: This should be removed by the first stable release.
|
||||
if fi.IsDir() {
|
||||
log.Fatal("Your package cache database is using the old database engine. Please remove ~/.cache/lure and then run `lure ref`.").Send()
|
||||
}
|
||||
}
|
||||
|
||||
gdb, err = sqlx.Open("sqlite", config.DBPath)
|
||||
var err error
|
||||
gdb, err = db.Open(config.DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening database").Err(err).Send()
|
||||
}
|
||||
|
||||
err = db.Init(gdb)
|
||||
if err != nil {
|
||||
log.Fatal("Error initializing database").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
@@ -49,6 +67,7 @@ func main() {
|
||||
lureWebAPI{db: gdb},
|
||||
twirp.WithServerPathPrefix(""),
|
||||
)
|
||||
handler = withAcceptLanguage(handler)
|
||||
handler = allowAllCORSHandler(handler)
|
||||
handler = handleWebhook(handler, sigCh)
|
||||
|
||||
@@ -75,3 +94,24 @@ func allowAllCORSHandler(h http.Handler) http.Handler {
|
||||
h.ServeHTTP(res, req)
|
||||
})
|
||||
}
|
||||
|
||||
type (
|
||||
acceptLanguageKey struct{}
|
||||
langParameterKey struct{}
|
||||
)
|
||||
|
||||
func withAcceptLanguage(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
|
||||
langs := req.Header.Get("Accept-Language")
|
||||
ctx = context.WithValue(ctx, acceptLanguageKey{}, langs)
|
||||
|
||||
lang := req.URL.Query().Get("lang")
|
||||
ctx = context.WithValue(ctx, langParameterKey{}, lang)
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
h.ServeHTTP(res, req)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
@@ -51,7 +69,7 @@ func verifySecure(req *http.Request) error {
|
||||
|
||||
secretStr, ok := os.LookupEnv("LURE_API_GITHUB_SECRET")
|
||||
if !ok {
|
||||
return errors.New("LURE_API_GITHUB_SECRET must be set to the secret used for setting up the github webhook")
|
||||
return errors.New("LURE_API_GITHUB_SECRET must be set to the secret used for setting up the github webhook\n\n")
|
||||
}
|
||||
secret := []byte(secretStr)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
36
db.go
36
db.go
@@ -1,8 +1,24 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
"os"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
@@ -12,21 +28,9 @@ import (
|
||||
var gdb *sqlx.DB
|
||||
|
||||
func init() {
|
||||
fi, err := os.Stat(config.DBPath)
|
||||
if err == nil {
|
||||
// TODO: This should be removed by the first stable release.
|
||||
if fi.IsDir() {
|
||||
log.Fatal("Your package cache database is using the old database engine. Please remove ~/.cache/lure and then run `lure ref`.").Send()
|
||||
}
|
||||
}
|
||||
|
||||
gdb, err = sqlx.Open("sqlite", config.DBPath)
|
||||
var err error
|
||||
gdb, err = db.Open(config.DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening database").Err(err).Send()
|
||||
}
|
||||
|
||||
err = db.Init(gdb)
|
||||
if err != nil {
|
||||
log.Fatal("Error initializing database").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -182,20 +182,19 @@ https://example.com/archive.tar.gz?~archive=false
|
||||
|
||||
If the URL scheme starts with `git+`, the source will be downloaded as a git repo. The git download mode supports multiple parameters:
|
||||
|
||||
- `~tag`: Specify which tag of the repo to check out.
|
||||
- `~branch`: Specify which branch of the repo to check out.
|
||||
- `~commit`: Specify which commit of the repo to check out.
|
||||
- `~rev`: Specify which revision of the repo to check out.
|
||||
- `~depth`: Specify what depth should be used when cloning the repo. Must be an integer.
|
||||
- `~name`: Specify the name of the directory into which the git repo should be cloned.
|
||||
- `~recursive`: If set to true, submodules will be cloned recursively. It is false by default.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
git+https://gitea.arsenm.dev/Arsen6331/itd?~branch=resource-loading&~depth=1
|
||||
git+https://gitea.arsenm.dev/Arsen6331/itd?~rev=resource-loading&~depth=1
|
||||
```
|
||||
|
||||
```text
|
||||
git+https://gitea.arsenm.dev/Arsen6331/lure?~tag=v0.0.1
|
||||
git+https://gitea.arsenm.dev/Arsen6331/lure?~rev=v0.0.1&~recursive=true
|
||||
```
|
||||
|
||||
### checksums
|
||||
|
||||
@@ -31,12 +31,15 @@ The package arguments do not have to be exact. LURE will check the `provides` ar
|
||||
|
||||
If multiple packages are found, you will be prompted to select which you want to install.
|
||||
|
||||
By default, if a package has already been built, LURE will install the cached package rather than re-build it. Use the `-c` or `--clean` flag to force a re-build.
|
||||
|
||||
Examples:
|
||||
|
||||
```shell
|
||||
lure in itd-bin # only finds itd-bin
|
||||
lure in itd # finds itd-bin and itd-git
|
||||
lure in it% # finds itd-bin, itd-git, and itgui-git
|
||||
lure in -c itd-bin
|
||||
```
|
||||
|
||||
### remove
|
||||
@@ -53,6 +56,8 @@ lure rm firefox
|
||||
|
||||
The upgrade command looks through the packages installed on your system and sees if any of them match LURE repo packages. If they do, their versions are compared using the `rpmvercmp` algorithm. If LURE repos contain a newer version, the package is upgraded.
|
||||
|
||||
By default, if a package has already been built, LURE will install the cached package rather than re-build it. Use the `-c` or `--clean` flag to force a re-build.
|
||||
|
||||
Example:
|
||||
|
||||
```shell
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
*
|
||||
* 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 download
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/mholt/archiver/v4"
|
||||
)
|
||||
|
||||
var ErrChecksumMismatch = errors.New("checksums did not match")
|
||||
|
||||
type GetOptions struct {
|
||||
SourceURL string
|
||||
Destination string
|
||||
SHA256Sum []byte
|
||||
// EncloseGit determines if Get will create an enclosing
|
||||
// directory for git repos
|
||||
EncloseGit bool
|
||||
}
|
||||
|
||||
// Get downloads from a URL
|
||||
func Get(ctx context.Context, opts GetOptions) error {
|
||||
dest, err := filepath.Abs(opts.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Destination = dest
|
||||
|
||||
err = os.MkdirAll(opts.Destination, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := url.Parse(opts.SourceURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query := src.Query()
|
||||
|
||||
if strings.HasPrefix(src.Scheme, "git+") {
|
||||
err = getGit(ctx, src, query, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = getFile(ctx, src, query, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGit(ctx context.Context, src *url.URL, query url.Values, opts GetOptions) (err error) {
|
||||
tag := query.Get("~tag")
|
||||
query.Del("~tag")
|
||||
|
||||
branch := query.Get("~branch")
|
||||
query.Del("~branch")
|
||||
|
||||
commit := query.Get("~commit")
|
||||
query.Del("~commit")
|
||||
|
||||
depthStr := query.Get("~depth")
|
||||
query.Del("~depth")
|
||||
|
||||
name := query.Get("~name")
|
||||
query.Del("~name")
|
||||
|
||||
var refName plumbing.ReferenceName
|
||||
if tag != "" {
|
||||
refName = plumbing.NewTagReferenceName(tag)
|
||||
} else if branch != "" {
|
||||
refName = plumbing.NewBranchReferenceName(branch)
|
||||
}
|
||||
|
||||
src.Scheme = strings.TrimPrefix(src.Scheme, "git+")
|
||||
src.RawQuery = query.Encode()
|
||||
|
||||
if name == "" {
|
||||
name = path.Base(src.Path)
|
||||
name = strings.TrimSuffix(name, ".git")
|
||||
}
|
||||
|
||||
dstDir := opts.Destination
|
||||
if opts.EncloseGit {
|
||||
dstDir = filepath.Join(opts.Destination, name)
|
||||
}
|
||||
|
||||
depth := 0
|
||||
if depthStr != "" {
|
||||
depth, err = strconv.Atoi(depthStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cloneOpts := &git.CloneOptions{
|
||||
URL: src.String(),
|
||||
Progress: os.Stderr,
|
||||
Depth: depth,
|
||||
}
|
||||
|
||||
repo, err := git.PlainCloneContext(ctx, dstDir, false, cloneOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkoutOpts := &git.CheckoutOptions{}
|
||||
if refName != "" {
|
||||
checkoutOpts.Branch = refName
|
||||
} else if commit != "" {
|
||||
checkoutOpts.Hash = plumbing.NewHash(commit)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return w.Checkout(checkoutOpts)
|
||||
}
|
||||
|
||||
func getFile(ctx context.Context, src *url.URL, query url.Values, opts GetOptions) error {
|
||||
name := query.Get("~name")
|
||||
query.Del("~name")
|
||||
|
||||
archive := query.Get("~archive")
|
||||
query.Del("~archive")
|
||||
|
||||
src.RawQuery = query.Encode()
|
||||
|
||||
if name == "" {
|
||||
name = path.Base(src.Path)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, src.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
|
||||
format, input, err := archiver.Identify(name, res.Body)
|
||||
if err == archiver.ErrNoMatch || archive == "false" {
|
||||
fl, err := os.Create(filepath.Join(opts.Destination, name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := io.MultiWriter(hash, fl)
|
||||
|
||||
_, err = io.Copy(w, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Body.Close()
|
||||
fl.Close()
|
||||
|
||||
if opts.SHA256Sum != nil {
|
||||
sum := hash.Sum(nil)
|
||||
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||
return ErrChecksumMismatch
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else {
|
||||
err = extractFile(ctx, input, hash, format, name, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFile(ctx context.Context, input io.Reader, hash hash.Hash, format archiver.Format, name string, opts GetOptions) (err error) {
|
||||
r := io.TeeReader(input, hash)
|
||||
fname := format.Name()
|
||||
|
||||
switch format := format.(type) {
|
||||
case archiver.Extractor:
|
||||
err = format.Extract(ctx, r, nil, func(ctx context.Context, f archiver.File) error {
|
||||
fr, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fr.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fm := fi.Mode()
|
||||
|
||||
path := filepath.Join(opts.Destination, f.NameInArchive)
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(path), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
err = os.Mkdir(path, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
outFl, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fm.Perm())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFl.Close()
|
||||
|
||||
_, err = io.Copy(outFl, fr)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case archiver.Decompressor:
|
||||
rc, err := format.OpenReader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
path := filepath.Join(opts.Destination, name)
|
||||
path = strings.TrimSuffix(path, fname)
|
||||
|
||||
outFl, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFl, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.SHA256Sum != nil {
|
||||
sum := hash.Sum(nil)
|
||||
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||
return ErrChecksumMismatch
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
29
fix.go
29
fix.go
@@ -1,9 +1,26 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
"os"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
@@ -28,18 +45,12 @@ func fixCmd(c *cli.Context) error {
|
||||
log.Fatal("Unable to create new cache directory").Err(err).Send()
|
||||
}
|
||||
|
||||
gdb, err = sqlx.Open("sqlite", config.DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create new database").Err(err).Send()
|
||||
}
|
||||
|
||||
// Make sure the DB is rebuilt when repos are pulled
|
||||
config.DBPresent = false
|
||||
|
||||
err = db.Init(gdb)
|
||||
gdb, err = db.Open(config.DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error initializing database").Err(err).Send()
|
||||
}
|
||||
config.DBPresent = false
|
||||
|
||||
err = repos.Pull(c.Context, gdb, cfg.Repos)
|
||||
if err != nil {
|
||||
|
||||
22
go.mod
22
go.mod
@@ -4,6 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/PuerkitoBio/purell v1.2.0
|
||||
github.com/alecthomas/chroma/v2 v2.4.0
|
||||
github.com/charmbracelet/bubbles v0.14.0
|
||||
github.com/charmbracelet/bubbletea v0.23.1
|
||||
@@ -15,12 +16,16 @@ require (
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/pelletier/go-toml/v2 v2.0.5
|
||||
github.com/pelletier/go-toml/v2 v2.0.6
|
||||
github.com/schollz/progressbar/v3 v3.13.0
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible
|
||||
github.com/urfave/cli/v2 v2.16.3
|
||||
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668
|
||||
github.com/urfave/cli/v2 v2.23.7
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||
go.arsenm.dev/logger v0.0.0-20230126004036-a8cbbe3b6fe6
|
||||
go.arsenm.dev/translate v0.0.0-20230113025904-5ad1ec0ed296
|
||||
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
|
||||
golang.org/x/sys v0.3.0
|
||||
golang.org/x/sys v0.4.0
|
||||
golang.org/x/text v0.6.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.20.0
|
||||
@@ -62,10 +67,11 @@ require (
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.11 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // 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/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
@@ -76,12 +82,13 @@ require (
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.3 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
@@ -90,8 +97,7 @@ require (
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/term v0.4.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
|
||||
46
go.sum
46
go.sum
@@ -23,6 +23,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c h1:bNpaLLv2Y4
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f h1:CGq7OieOz3wyQJ1fO8S0eO9TCW1JyvLrf8fhzz1i8ko=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
|
||||
github.com/PuerkitoBio/purell v1.2.0 h1:/Jdm5QfyM8zdlqT6WVZU4cfP23sot6CEHA4CS49Ezig=
|
||||
github.com/PuerkitoBio/purell v1.2.0/go.mod h1:OhLRTaaIzhvIyofkJfB24gokC7tM42Px5UhoT32THBk=
|
||||
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
||||
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/alecthomas/assert/v2 v2.2.0 h1:f6L/b7KE2bfA+9O4FL3CM/xJccDEwPVYd5fALBiuwvw=
|
||||
@@ -123,6 +125,7 @@ github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
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/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
@@ -156,8 +159,9 @@ github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHR
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
@@ -171,6 +175,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/mholt/archiver/v4 v4.0.0-alpha.7 h1:xzByj8G8tj0Oq7ZYYU4+ixL/CVb5ruWCm0EZQ1PjOkE=
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7/go.mod h1:Fs8qUkO74HHaidabihzYephJH8qmGD/nCP6tE5xC9BM=
|
||||
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.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@@ -194,8 +200,8 @@ github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4Y
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -206,13 +212,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
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/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/schollz/progressbar/v3 v3.13.0 h1:9TeeWRcjW2qd05I8Kf9knPkW4vLM/hYoa6z9ABvxje8=
|
||||
github.com/schollz/progressbar/v3 v3.13.0/go.mod h1:ZBYnSuLAX2LU8P8UiKN/KgF2DY58AJC8yfVYLPC8Ly4=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
@@ -225,13 +234,16 @@ github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hg
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
|
||||
@@ -239,8 +251,12 @@ github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vF
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
|
||||
github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
||||
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY=
|
||||
github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
||||
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
|
||||
@@ -252,8 +268,10 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
|
||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
|
||||
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668 h1:7dSmQ79slzFpcii8zgQbEStxpkTPvq3tzWc7KX5uwGc=
|
||||
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
|
||||
go.arsenm.dev/logger v0.0.0-20230126004036-a8cbbe3b6fe6 h1:p5EkGdoRX4ddMJcYQzRPTHk8oVyMv0q8agaAbPr/oak=
|
||||
go.arsenm.dev/logger v0.0.0-20230126004036-a8cbbe3b6fe6/go.mod h1:Sg0thD0AZwMib+fD+YFOgmTWwEOqPbMjSSmWef6mEog=
|
||||
go.arsenm.dev/translate v0.0.0-20230113025904-5ad1ec0ed296 h1:uOJuOOn/sPe4YX9MD98tCoeLQTopIk17dJt0fwCeJrk=
|
||||
go.arsenm.dev/translate v0.0.0-20230113025904-5ad1ec0ed296/go.mod h1:+rZV+tkYEPgZyP0OWBH477vWNwxN3pcAcukcjzgQjco=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
@@ -290,15 +308,17 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
||||
18
helpers.go
18
helpers.go
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
|
||||
44
info.go
44
info.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/internal/cliutils"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
"go.arsenm.dev/lure/internal/overrides"
|
||||
"go.arsenm.dev/lure/internal/repos"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -52,7 +53,7 @@ func infoCmd(c *cli.Context) error {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
pkgs := cliutils.FlattenPkgs(found, "show")
|
||||
pkgs := cliutils.FlattenPkgs(found, "show", translator)
|
||||
|
||||
var names []string
|
||||
all := c.Bool("all")
|
||||
@@ -62,38 +63,29 @@ func infoCmd(c *cli.Context) error {
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||
}
|
||||
names = overrides.Resolve(info, overrides.DefaultOpts)
|
||||
names, err = overrides.Resolve(
|
||||
info,
|
||||
overrides.DefaultOpts.
|
||||
WithLanguages([]string{config.SystemLang()}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal("Error resolving overrides").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
if !all {
|
||||
depsSet := false
|
||||
buildDepsSet := false
|
||||
for _, name := range names {
|
||||
if deps, ok := pkg.Depends.Val[name]; ok && !depsSet {
|
||||
pkg.Depends.Val = map[string][]string{name: deps}
|
||||
depsSet = true
|
||||
}
|
||||
|
||||
if buildDeps, ok := pkg.BuildDepends.Val[name]; ok && !buildDepsSet {
|
||||
pkg.BuildDepends.Val = map[string][]string{name: buildDeps}
|
||||
buildDepsSet = true
|
||||
}
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||
}
|
||||
|
||||
if !depsSet {
|
||||
pkg.Depends.Val = nil
|
||||
}
|
||||
|
||||
if !buildDepsSet {
|
||||
pkg.BuildDepends.Val = nil
|
||||
} else {
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||
}
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
|
||||
12
install.go
12
install.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -53,13 +53,13 @@ func installCmd(c *cli.Context) error {
|
||||
log.Fatal("Error finding packages").Err(err).Send()
|
||||
}
|
||||
|
||||
installPkgs(c.Context, cliutils.FlattenPkgs(found, "install"), notFound, mgr)
|
||||
installPkgs(c.Context, cliutils.FlattenPkgs(found, "install", translator), notFound, mgr, c.Bool("clean"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// installPkgs installs non-LURE packages via the package manager, then builds and installs LURE
|
||||
// packages
|
||||
func installPkgs(ctx context.Context, pkgs []db.Package, notFound []string, mgr manager.Manager) {
|
||||
func installPkgs(ctx context.Context, pkgs []db.Package, notFound []string, mgr manager.Manager, clean bool) {
|
||||
if len(notFound) > 0 {
|
||||
err := mgr.Install(nil, notFound...)
|
||||
if err != nil {
|
||||
@@ -67,7 +67,7 @@ func installPkgs(ctx context.Context, pkgs []db.Package, notFound []string, mgr
|
||||
}
|
||||
}
|
||||
|
||||
installScripts(ctx, mgr, getScriptPaths(pkgs))
|
||||
installScripts(ctx, mgr, getScriptPaths(pkgs), clean)
|
||||
}
|
||||
|
||||
// getScriptPaths generates a slice of script paths corresponding to the
|
||||
@@ -82,9 +82,9 @@ func getScriptPaths(pkgs []db.Package) []string {
|
||||
}
|
||||
|
||||
// installScripts builds and installs LURE build scripts
|
||||
func installScripts(ctx context.Context, mgr manager.Manager, scripts []string) {
|
||||
func installScripts(ctx context.Context, mgr manager.Manager, scripts []string, clean bool) {
|
||||
for _, script := range scripts {
|
||||
builtPkgs, _, err := buildPackage(ctx, script, mgr)
|
||||
builtPkgs, _, err := buildPackage(ctx, script, mgr, clean)
|
||||
if err != nil {
|
||||
log.Fatal("Error building package").Err(err).Send()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 api
|
||||
|
||||
//go:generate protoc --twirp_out=. lure.proto
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cliutils
|
||||
|
||||
import (
|
||||
@@ -5,16 +23,18 @@ import (
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
"go.arsenm.dev/lure/internal/db"
|
||||
"go.arsenm.dev/lure/internal/pager"
|
||||
"go.arsenm.dev/translate"
|
||||
)
|
||||
|
||||
// YesNoPrompt asks the user a yes or no question, using def as the default answer
|
||||
func YesNoPrompt(msg string, def bool) (bool, error) {
|
||||
func YesNoPrompt(msg string, def bool, t translate.Translator) (bool, error) {
|
||||
var answer bool
|
||||
err := survey.AskOne(
|
||||
&survey.Confirm{
|
||||
Message: msg,
|
||||
Message: t.TranslateTo(msg, config.Language),
|
||||
Default: def,
|
||||
},
|
||||
&answer,
|
||||
@@ -25,8 +45,8 @@ func YesNoPrompt(msg string, def bool) (bool, error) {
|
||||
// PromptViewScript asks the user if they'd like to see a script,
|
||||
// shows it if they answer yes, then asks if they'd still like to
|
||||
// continue, and exits if they answer no.
|
||||
func PromptViewScript(script, name, style string) error {
|
||||
view, err := YesNoPrompt("Would you like to view the build script for "+name, false)
|
||||
func PromptViewScript(script, name, style string, t translate.Translator) error {
|
||||
view, err := YesNoPrompt(t.TranslateTo("Would you like to view the build script for", config.Language)+" "+name, false, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -37,13 +57,13 @@ func PromptViewScript(script, name, style string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cont, err := YesNoPrompt("Would you still like to continue?", false)
|
||||
cont, err := YesNoPrompt("Would you still like to continue?", false, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cont {
|
||||
log.Fatal("User chose not to continue after reading script").Send()
|
||||
log.Fatal(t.TranslateTo("User chose not to continue after reading script", config.Language)).Send()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,11 +90,11 @@ func ShowScript(path, name, style string) error {
|
||||
|
||||
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
|
||||
// of packages by prompting the user if multiple packages match.
|
||||
func FlattenPkgs(found map[string][]db.Package, verb string) []db.Package {
|
||||
func FlattenPkgs(found map[string][]db.Package, verb string, t translate.Translator) []db.Package {
|
||||
var outPkgs []db.Package
|
||||
for _, pkgs := range found {
|
||||
if len(pkgs) > 1 {
|
||||
choices, err := PkgPrompt(pkgs, verb)
|
||||
choices, err := PkgPrompt(pkgs, verb, t)
|
||||
if err != nil {
|
||||
log.Fatal("Error prompting for choice of package").Send()
|
||||
}
|
||||
@@ -88,7 +108,7 @@ func FlattenPkgs(found map[string][]db.Package, verb string) []db.Package {
|
||||
|
||||
// PkgPrompt asks the user to choose between multiple packages.
|
||||
// The user may choose multiple packages.
|
||||
func PkgPrompt(options []db.Package, verb string) ([]db.Package, error) {
|
||||
func PkgPrompt(options []db.Package, verb string, t translate.Translator) ([]db.Package, error) {
|
||||
names := make([]string, len(options))
|
||||
for i, option := range options {
|
||||
names[i] = option.Repository + "/" + option.Name + " " + option.Version
|
||||
@@ -96,7 +116,7 @@ func PkgPrompt(options []db.Package, verb string) ([]db.Package, error) {
|
||||
|
||||
prompt := &survey.MultiSelect{
|
||||
Options: names,
|
||||
Message: "Choose which package(s) to " + verb,
|
||||
Message: t.TranslateTo("Choose which package(s) to "+verb, config.Language),
|
||||
}
|
||||
|
||||
var choices []int
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -71,6 +89,6 @@ func init() {
|
||||
|
||||
DBPath = filepath.Join(CacheDir, "db")
|
||||
|
||||
_, err = os.ReadDir(DBPath)
|
||||
DBPresent = err == nil
|
||||
fi, err := os.Stat(DBPath)
|
||||
DBPresent = err == nil && !fi.IsDir()
|
||||
}
|
||||
|
||||
48
internal/config/lang.go
Normal file
48
internal/config/lang.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"go.arsenm.dev/logger/log"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var Language language.Tag
|
||||
|
||||
func init() {
|
||||
lang := SystemLang()
|
||||
tag, err := language.Parse(lang)
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing system language").Err(err).Send()
|
||||
}
|
||||
base, _ := tag.Base()
|
||||
Language = language.Make(base.String())
|
||||
}
|
||||
|
||||
func SystemLang() string {
|
||||
lang := os.Getenv("LANG")
|
||||
lang, _, _ = strings.Cut(lang, ".")
|
||||
if lang == "" {
|
||||
lang = "en"
|
||||
}
|
||||
return lang
|
||||
}
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import _ "embed"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
@@ -6,12 +24,17 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
"golang.org/x/exp/slices"
|
||||
"modernc.org/sqlite"
|
||||
)
|
||||
|
||||
const CurrentVersion = 1
|
||||
|
||||
func init() {
|
||||
sqlite.MustRegisterScalarFunction("json_array_contains", 2, JsonArrayContains)
|
||||
}
|
||||
@@ -22,9 +45,9 @@ type Package struct {
|
||||
Version string `sh:"version,required" db:"version"`
|
||||
Release int `sh:"release,required" db:"release"`
|
||||
Epoch uint `sh:"epoch" db:"epoch"`
|
||||
Description string `sh:"desc" db:"description"`
|
||||
Homepage string `sh:"homepage" db:"homepage"`
|
||||
Maintainer string `sh:"maintainer" db:"maintainer"`
|
||||
Description JSON[map[string]string] `db:"description"`
|
||||
Homepage JSON[map[string]string] `db:"homepage"`
|
||||
Maintainer JSON[map[string]string] `db:"maintainer"`
|
||||
Architectures JSON[[]string] `sh:"architectures" db:"architectures"`
|
||||
Licenses JSON[[]string] `sh:"license" db:"licenses"`
|
||||
Provides JSON[[]string] `sh:"provides" db:"provides"`
|
||||
@@ -35,8 +58,42 @@ type Package struct {
|
||||
Repository string `db:"repository"`
|
||||
}
|
||||
|
||||
type version struct {
|
||||
Version int `db:"version"`
|
||||
}
|
||||
|
||||
func Open(dsn string) (*sqlx.DB, error) {
|
||||
if dsn != ":memory:" {
|
||||
fi, err := os.Stat(config.DBPath)
|
||||
if err == nil {
|
||||
// TODO: This should be removed by the first stable release.
|
||||
if fi.IsDir() {
|
||||
log.Warn("Your database is using the old database engine; rebuilding").Send()
|
||||
|
||||
err = os.RemoveAll(config.DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error removing old database").Err(err).Send()
|
||||
}
|
||||
config.DBPresent = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db, err := sqlx.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening database").Err(err).Send()
|
||||
}
|
||||
|
||||
err = Init(db, dsn)
|
||||
if err != nil {
|
||||
log.Fatal("Error initializing database").Err(err).Send()
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Init initializes the database
|
||||
func Init(db *sqlx.DB) error {
|
||||
func Init(db *sqlx.DB, dsn string) error {
|
||||
*db = *db.Unsafe()
|
||||
_, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS pkgs (
|
||||
@@ -45,9 +102,9 @@ func Init(db *sqlx.DB) error {
|
||||
version TEXT NOT NULL,
|
||||
release INT NOT NULL,
|
||||
epoch INT,
|
||||
description TEXT,
|
||||
homepage TEXT,
|
||||
maintainer TEXT,
|
||||
description TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')),
|
||||
homepage TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')),
|
||||
maintainer TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')),
|
||||
architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')),
|
||||
licenses TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')),
|
||||
provides TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')),
|
||||
@@ -57,7 +114,52 @@ func Init(db *sqlx.DB) error {
|
||||
builddepends TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')),
|
||||
UNIQUE(name, repository)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS lure_db_version (
|
||||
version INT NOT NULL
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ver, ok := GetVersion(db)
|
||||
if !ok {
|
||||
log.Warn("Database version does not exist. Run lure fix if something isn't working.").Send()
|
||||
return addVersion(db, CurrentVersion)
|
||||
}
|
||||
|
||||
if ver != CurrentVersion {
|
||||
log.Warn("Database version mismatch; rebuilding").Int("version", ver).Int("expected", CurrentVersion).Send()
|
||||
|
||||
db.Close()
|
||||
err = os.Remove(config.DBPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.DBPresent = false
|
||||
|
||||
tdb, err := Open(dsn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*db = *tdb
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetVersion(db *sqlx.DB) (int, bool) {
|
||||
var ver version
|
||||
err := db.Get(&ver, "SELECT * FROM lure_db_version LIMIT 1;")
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return ver.Version, true
|
||||
}
|
||||
|
||||
func addVersion(db *sqlx.DB, ver int) error {
|
||||
_, err := db.Exec(`INSERT INTO lure_db_version(version) VALUES (?);`, ver)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package db_test
|
||||
|
||||
import (
|
||||
@@ -10,13 +28,21 @@ import (
|
||||
)
|
||||
|
||||
var testPkg = db.Package{
|
||||
Name: "test",
|
||||
Version: "0.0.1",
|
||||
Release: 1,
|
||||
Epoch: 2,
|
||||
Description: "Test package",
|
||||
Homepage: "https://lure.arsenm.dev",
|
||||
Maintainer: "Arsen Musayelyan <arsen@arsenm.dev>",
|
||||
Name: "test",
|
||||
Version: "0.0.1",
|
||||
Release: 1,
|
||||
Epoch: 2,
|
||||
Description: db.NewJSON(map[string]string{
|
||||
"en": "Test package",
|
||||
"ru": "Проверочный пакет",
|
||||
}),
|
||||
Homepage: db.NewJSON(map[string]string{
|
||||
"en": "https://lure.arsenm.dev",
|
||||
}),
|
||||
Maintainer: db.NewJSON(map[string]string{
|
||||
"en": "Arsen Musayelyan <arsen@arsenm.dev>",
|
||||
"ru": "Арсен Мусаелян <arsen@arsenm.dev>",
|
||||
}),
|
||||
Architectures: db.NewJSON([]string{"arm64", "amd64"}),
|
||||
Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
|
||||
Provides: db.NewJSON([]string{"test"}),
|
||||
@@ -32,18 +58,6 @@ var testPkg = db.Package{
|
||||
Repository: "default",
|
||||
}
|
||||
|
||||
func getDB(t *testing.T) (*sqlx.DB, error) {
|
||||
t.Helper()
|
||||
|
||||
gdb, err := sqlx.Open("sqlite", ":memory:")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.Init(gdb)
|
||||
return gdb, err
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
gdb, err := sqlx.Open("sqlite", ":memory:")
|
||||
if err != nil {
|
||||
@@ -51,7 +65,7 @@ func TestInit(t *testing.T) {
|
||||
}
|
||||
defer gdb.Close()
|
||||
|
||||
err = db.Init(gdb)
|
||||
err = db.Init(gdb, ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
@@ -60,10 +74,17 @@ func TestInit(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
ver, ok := db.GetVersion(gdb)
|
||||
if !ok {
|
||||
t.Errorf("Expected version to be present")
|
||||
} else if ver != db.CurrentVersion {
|
||||
t.Errorf("Expected version %d, got %d", db.CurrentVersion, ver)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertPackage(t *testing.T) {
|
||||
gdb, err := getDB(t)
|
||||
gdb, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
@@ -86,7 +107,7 @@ func TestInsertPackage(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetPkgs(t *testing.T) {
|
||||
gdb, err := getDB(t)
|
||||
gdb, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
@@ -126,7 +147,7 @@ func TestGetPkgs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetPkg(t *testing.T) {
|
||||
gdb, err := getDB(t)
|
||||
gdb, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
@@ -162,7 +183,7 @@ func TestGetPkg(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeletePkgs(t *testing.T) {
|
||||
gdb, err := getDB(t)
|
||||
gdb, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
@@ -200,7 +221,7 @@ func TestDeletePkgs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJsonArrayContains(t *testing.T) {
|
||||
gdb, err := getDB(t)
|
||||
gdb, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
325
internal/dl/dl.go
Normal file
325
internal/dl/dl.go
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 contains abstractions for downloadingfiles and directories
|
||||
// from various sources.
|
||||
package dl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/PuerkitoBio/purell"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/lure/internal/dlcache"
|
||||
)
|
||||
|
||||
const manifestFileName = ".lure_cache_manifest"
|
||||
|
||||
// ErrChecksumMismatch occurs when the checksum of a downloaded file
|
||||
// does not match the expected checksum provided in the Options struct.
|
||||
var ErrChecksumMismatch = errors.New("dl: checksums did not match")
|
||||
|
||||
// Downloaders contains all the downloaders in the order in which
|
||||
// they should be checked
|
||||
var Downloaders = []Downloader{
|
||||
GitDownloader{},
|
||||
FileDownloader{},
|
||||
}
|
||||
|
||||
// Type represents the type of download (file or directory)
|
||||
type Type uint8
|
||||
|
||||
const (
|
||||
TypeFile Type = iota
|
||||
TypeDir
|
||||
)
|
||||
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case TypeFile:
|
||||
return "file"
|
||||
case TypeDir:
|
||||
return "dir"
|
||||
}
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
// Options contains the options for downloading
|
||||
// files and directories
|
||||
type Options struct {
|
||||
SHA256 []byte
|
||||
Name string
|
||||
URL string
|
||||
Destination string
|
||||
CacheDisabled bool
|
||||
PostprocDisabled bool
|
||||
Progress io.Writer
|
||||
}
|
||||
|
||||
// Manifest holds information about the type and name
|
||||
// of a downloaded file or directory. It is stored inside
|
||||
// each cache directory for later use.
|
||||
type Manifest struct {
|
||||
Type Type
|
||||
Name string
|
||||
}
|
||||
|
||||
type Downloader interface {
|
||||
// Name returns the name of the downloader
|
||||
Name() string
|
||||
// MatchURL checks if the given URL matches
|
||||
// the downloader.
|
||||
MatchURL(string) bool
|
||||
// Download downloads the object at the URL
|
||||
// provided in the options, to the destination
|
||||
// given in the options. It returns a type,
|
||||
// a name for the downloaded object (this may be empty),
|
||||
// and an error.
|
||||
Download(Options) (Type, string, error)
|
||||
}
|
||||
|
||||
// UpdatingDownloader extends the Downloader interface
|
||||
// with an Update method for protocols such as git, which
|
||||
// allow for incremental updates without changing the URL.
|
||||
type UpdatingDownloader interface {
|
||||
Downloader
|
||||
// Update checks for and performs any
|
||||
// available updates for the object
|
||||
// described in the options. It returns
|
||||
// true if an update was performed, or
|
||||
// false if no update was required.
|
||||
Update(Options) (bool, error)
|
||||
}
|
||||
|
||||
// Download downloads a file or directory using the specified options.
|
||||
// It first gets the appropriate downloader for the URL, then checks
|
||||
// if caching is enabled. If caching is enabled, it attempts to get
|
||||
// the cache directory for the URL and update it if necessary.
|
||||
// If the source is found in the cache, it links it to the destination
|
||||
// using hard links. If the source is not found in the cache,
|
||||
// it downloads the source to a new cache directory and links it
|
||||
// to the destination.
|
||||
func Download(ctx context.Context, opts Options) (err error) {
|
||||
normalized, err := normalizeURL(opts.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.URL = normalized
|
||||
|
||||
d := getDownloader(opts.URL)
|
||||
|
||||
if opts.CacheDisabled {
|
||||
_, _, err = d.Download(opts)
|
||||
return err
|
||||
}
|
||||
|
||||
var t Type
|
||||
cacheDir, ok := dlcache.Get(opts.URL)
|
||||
if ok {
|
||||
var updated bool
|
||||
if d, ok := d.(UpdatingDownloader); ok {
|
||||
log.Info("Source can be updated, updating if required").Str("source", opts.Name).Str("downloader", d.Name()).Send()
|
||||
|
||||
updated, err = d.Update(Options{
|
||||
Name: opts.Name,
|
||||
URL: opts.URL,
|
||||
Destination: cacheDir,
|
||||
Progress: opts.Progress,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
m, err := getManifest(cacheDir)
|
||||
if err == nil {
|
||||
t = m.Type
|
||||
|
||||
dest := filepath.Join(opts.Destination, m.Name)
|
||||
ok, err := handleCache(cacheDir, dest, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok && !updated {
|
||||
log.Info("Source found in cache, linked to destination").Str("source", opts.Name).Stringer("type", t).Send()
|
||||
return nil
|
||||
} else if ok {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// If we cannot read the manifest,
|
||||
// this cache entry is invalid and
|
||||
// the source must be re-downloaded.
|
||||
err = os.RemoveAll(cacheDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Downloading source").Str("source", opts.Name).Str("downloader", d.Name()).Send()
|
||||
|
||||
cacheDir, err = dlcache.New(opts.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t, name, err := d.Download(Options{
|
||||
Name: opts.Name,
|
||||
URL: opts.URL,
|
||||
Destination: cacheDir,
|
||||
Progress: opts.Progress,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = writeManifest(cacheDir, Manifest{t, name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest := filepath.Join(opts.Destination, name)
|
||||
_, err = handleCache(cacheDir, dest, t)
|
||||
return err
|
||||
}
|
||||
|
||||
// writeManifest writes the manifest to the specified cache directory.
|
||||
func writeManifest(cacheDir string, m Manifest) error {
|
||||
fl, err := os.Create(filepath.Join(cacheDir, manifestFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fl.Close()
|
||||
return msgpack.NewEncoder(fl).Encode(m)
|
||||
}
|
||||
|
||||
// getManifest reads the manifest from the specified cache directory.
|
||||
func getManifest(cacheDir string) (m Manifest, err error) {
|
||||
fl, err := os.Open(filepath.Join(cacheDir, manifestFileName))
|
||||
if err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
defer fl.Close()
|
||||
|
||||
err = msgpack.NewDecoder(fl).Decode(&m)
|
||||
return
|
||||
}
|
||||
|
||||
// handleCache links the cache directory or a file within it to the destination
|
||||
func handleCache(cacheDir, dest string, t Type) (bool, error) {
|
||||
switch t {
|
||||
case TypeFile:
|
||||
cd, err := os.Open(cacheDir)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
names, err := cd.Readdirnames(2)
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
cd.Close()
|
||||
|
||||
for _, name := range names {
|
||||
if name == manifestFileName {
|
||||
continue
|
||||
}
|
||||
|
||||
err = os.Link(filepath.Join(cacheDir, names[0]), filepath.Join(dest, filepath.Base(names[0])))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
case TypeDir:
|
||||
err := linkDir(cacheDir, dest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// linkDir recursively walks through a directory, creating
|
||||
// hard links for each file from the src directory to the
|
||||
// dest directory. If it encounters a directory, it will
|
||||
// create a directory with the same name and permissions
|
||||
// in the dest directory, because hard links cannot be
|
||||
// created for directories.
|
||||
func linkDir(src, dest string) error {
|
||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.Name() == manifestFileName {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(src, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newPath := filepath.Join(dest, rel)
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(newPath, info.Mode())
|
||||
}
|
||||
|
||||
return os.Link(path, newPath)
|
||||
})
|
||||
}
|
||||
|
||||
func getDownloader(u string) Downloader {
|
||||
for _, d := range Downloaders {
|
||||
if d.MatchURL(u) {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeURL normalizes a URL string, so that insignificant
|
||||
// differences don't change the hash.
|
||||
func normalizeURL(u string) (string, error) {
|
||||
const normalizationFlags = purell.FlagRemoveTrailingSlash |
|
||||
purell.FlagRemoveDefaultPort |
|
||||
purell.FlagLowercaseHost |
|
||||
purell.FlagLowercaseScheme |
|
||||
purell.FlagRemoveDuplicateSlashes |
|
||||
purell.FlagRemoveFragment |
|
||||
purell.FlagRemoveUnnecessaryHostDots |
|
||||
purell.FlagSortQuery |
|
||||
purell.FlagDecodeHexHost |
|
||||
purell.FlagDecodeOctalHost |
|
||||
purell.FlagDecodeUnnecessaryEscapes |
|
||||
purell.FlagRemoveEmptyPortSeparator
|
||||
|
||||
return purell.NormalizeURLString(u, normalizationFlags)
|
||||
}
|
||||
243
internal/dl/file.go
Normal file
243
internal/dl/file.go
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"go.arsenm.dev/lure/internal/shutils"
|
||||
)
|
||||
|
||||
// FileDownloader downloads files using HTTP
|
||||
type FileDownloader struct{}
|
||||
|
||||
// Name always returns "file"
|
||||
func (FileDownloader) Name() string {
|
||||
return "file"
|
||||
}
|
||||
|
||||
// MatchURL always returns true, as FileDownloader
|
||||
// is used as a fallback if nothing else matches
|
||||
func (FileDownloader) MatchURL(string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Download downloads a file using HTTP. If the file is
|
||||
// compressed using a supported format, it will be extracted
|
||||
func (FileDownloader) Download(opts Options) (Type, string, error) {
|
||||
u, err := url.Parse(opts.URL)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
|
||||
name := query.Get("~name")
|
||||
query.Del("~name")
|
||||
|
||||
archive := query.Get("~archive")
|
||||
query.Del("~archive")
|
||||
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
res, err := http.Get(u.String())
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = getFilename(res)
|
||||
}
|
||||
|
||||
opts.PostprocDisabled = archive == "false"
|
||||
|
||||
path := filepath.Join(opts.Destination, name)
|
||||
fl, err := os.Create(path)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
defer fl.Close()
|
||||
|
||||
var bar io.WriteCloser
|
||||
if opts.Progress != nil {
|
||||
bar = progressbar.NewOptions64(
|
||||
res.ContentLength,
|
||||
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 {
|
||||
bar = shutils.NopRWC{}
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
|
||||
var w io.Writer
|
||||
if opts.SHA256 != nil {
|
||||
w = io.MultiWriter(fl, h, bar)
|
||||
} else {
|
||||
w = io.MultiWriter(fl, bar)
|
||||
}
|
||||
|
||||
_, err = io.Copy(w, res.Body)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
res.Body.Close()
|
||||
|
||||
if opts.SHA256 != nil {
|
||||
sum := h.Sum(nil)
|
||||
if !bytes.Equal(sum, opts.SHA256) {
|
||||
return 0, "", ErrChecksumMismatch
|
||||
}
|
||||
}
|
||||
|
||||
if opts.PostprocDisabled {
|
||||
return TypeFile, "", nil
|
||||
}
|
||||
|
||||
_, err = fl.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
format, r, err := archiver.Identify(name, fl)
|
||||
if err == archiver.ErrNoMatch {
|
||||
return TypeFile, "", nil
|
||||
} else if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
err = extractFile(r, format, name, opts)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
err = os.Remove(path)
|
||||
return TypeDir, "", err
|
||||
}
|
||||
|
||||
// extractFile extracts an archive or decompresses a file
|
||||
func extractFile(r io.Reader, format archiver.Format, name string, opts Options) (err error) {
|
||||
fname := format.Name()
|
||||
|
||||
switch format := format.(type) {
|
||||
case archiver.Extractor:
|
||||
err = format.Extract(context.Background(), r, nil, func(ctx context.Context, f archiver.File) error {
|
||||
fr, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fr.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fm := fi.Mode()
|
||||
|
||||
path := filepath.Join(opts.Destination, f.NameInArchive)
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(path), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
err = os.Mkdir(path, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
outFl, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fm.Perm())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFl.Close()
|
||||
|
||||
_, err = io.Copy(outFl, fr)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case archiver.Decompressor:
|
||||
rc, err := format.OpenReader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
path := filepath.Join(opts.Destination, name)
|
||||
path = strings.TrimSuffix(path, fname)
|
||||
|
||||
outFl, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFl, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var cdHeaderRgx = regexp.MustCompile(`filename="(.+)"`)
|
||||
|
||||
// getFilename attempts to parse the Content-Disposition
|
||||
// HTTP response header and extract a filename. If the
|
||||
// header does not exist, it will use the last element
|
||||
// of the path.
|
||||
func getFilename(res *http.Response) (name string) {
|
||||
cd := res.Header.Get("Content-Disposition")
|
||||
matches := cdHeaderRgx.FindStringSubmatch(cd)
|
||||
if len(matches) > 1 {
|
||||
name = matches[1]
|
||||
} else {
|
||||
name = path.Base(res.Request.URL.Path)
|
||||
}
|
||||
return name
|
||||
}
|
||||
180
internal/dl/git.go
Normal file
180
internal/dl/git.go
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
"errors"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
// GitDownloader downloads Git repositories
|
||||
type GitDownloader struct{}
|
||||
|
||||
// Name always returns "git"
|
||||
func (GitDownloader) Name() string {
|
||||
return "git"
|
||||
}
|
||||
|
||||
// MatchURL matches any URLs that start with "git+"
|
||||
func (GitDownloader) MatchURL(u string) bool {
|
||||
return strings.HasPrefix(u, "git+")
|
||||
}
|
||||
|
||||
// Download uses git to clone the repository from the specified URL.
|
||||
// It allows specifying the revision, depth and recursion options
|
||||
// via query string
|
||||
func (GitDownloader) Download(opts Options) (Type, string, error) {
|
||||
u, err := url.Parse(opts.URL)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
u.Scheme = strings.TrimPrefix(u.Scheme, "git+")
|
||||
|
||||
query := u.Query()
|
||||
|
||||
rev := query.Get("~rev")
|
||||
query.Del("~rev")
|
||||
|
||||
name := query.Get("~name")
|
||||
query.Del("~name")
|
||||
|
||||
depthStr := query.Get("~depth")
|
||||
query.Del("~depth")
|
||||
|
||||
recursive := query.Get("~recursive")
|
||||
query.Del("~recursive")
|
||||
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
depth := 0
|
||||
if depthStr != "" {
|
||||
depth, err = strconv.Atoi(depthStr)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
}
|
||||
|
||||
co := &git.CloneOptions{
|
||||
URL: u.String(),
|
||||
Depth: depth,
|
||||
Progress: opts.Progress,
|
||||
RecurseSubmodules: git.NoRecurseSubmodules,
|
||||
}
|
||||
|
||||
if recursive == "true" {
|
||||
co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
|
||||
}
|
||||
|
||||
r, err := git.PlainClone(opts.Destination, false, co)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
if rev != "" {
|
||||
h, err := r.ResolveRevision(plumbing.Revision(rev))
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
w, err := r.Worktree()
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
err = w.Checkout(&git.CheckoutOptions{
|
||||
Hash: *h,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = strings.TrimSuffix(path.Base(u.Path), ".git")
|
||||
}
|
||||
|
||||
return TypeDir, name, nil
|
||||
}
|
||||
|
||||
// Update uses git to pull the repository and update it
|
||||
// to the latest revision. It allows specifying the depth
|
||||
// and recursion options via query string. It returns
|
||||
// true if update was successful and false if the
|
||||
// repository is already up-to-date
|
||||
func (GitDownloader) Update(opts Options) (bool, error) {
|
||||
u, err := url.Parse(opts.URL)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
u.Scheme = strings.TrimPrefix(u.Scheme, "git+")
|
||||
|
||||
query := u.Query()
|
||||
query.Del("~rev")
|
||||
|
||||
depthStr := query.Get("~depth")
|
||||
query.Del("~depth")
|
||||
|
||||
recursive := query.Get("~recursive")
|
||||
query.Del("~recursive")
|
||||
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
r, err := git.PlainOpen(opts.Destination)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
w, err := r.Worktree()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
depth := 0
|
||||
if depthStr != "" {
|
||||
depth, err = strconv.Atoi(depthStr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
po := &git.PullOptions{
|
||||
Depth: depth,
|
||||
Progress: opts.Progress,
|
||||
RecurseSubmodules: git.NoRecurseSubmodules,
|
||||
}
|
||||
|
||||
if recursive == "true" {
|
||||
po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
|
||||
}
|
||||
|
||||
err = w.Pull(po)
|
||||
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
90
internal/dlcache/dlcache.go
Normal file
90
internal/dlcache/dlcache.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 dlcache
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
)
|
||||
|
||||
// BasePath stores the base path to the download cache
|
||||
var BasePath = filepath.Join(config.CacheDir, "dl")
|
||||
|
||||
// New creates a new directory with the given ID in the cache.
|
||||
// If a directory with the same ID already exists,
|
||||
// it will be deleted before creating a new one.
|
||||
func New(id string) (string, error) {
|
||||
h, err := hashID(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
itemPath := filepath.Join(BasePath, h)
|
||||
|
||||
fi, err := os.Stat(itemPath)
|
||||
if err == nil || (fi != nil && !fi.IsDir()) {
|
||||
err = os.RemoveAll(itemPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
err = os.MkdirAll(itemPath, 0o755)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return itemPath, nil
|
||||
}
|
||||
|
||||
// Get checks if an entry with the given ID
|
||||
// already exists in the cache, and if so,
|
||||
// returns the directory and true. If it
|
||||
// does not exist, it returns an empty string
|
||||
// and false.
|
||||
func Get(id string) (string, bool) {
|
||||
h, err := hashID(id)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
itemPath := filepath.Join(BasePath, h)
|
||||
|
||||
_, err = os.Stat(itemPath)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return itemPath, true
|
||||
}
|
||||
|
||||
// hashID hashes the input ID with SHA1
|
||||
// and returns the hex string of the hashed
|
||||
// ID.
|
||||
func hashID(id string) (string, error) {
|
||||
h := sha1.New()
|
||||
_, err := io.WriteString(h, id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
74
internal/dlcache/dlcache_test.go
Normal file
74
internal/dlcache/dlcache_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 dlcache_test
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"go.arsenm.dev/lure/internal/dlcache"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dir, err := os.MkdirTemp("/tmp", "lure-dlcache-test.*")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dlcache.BasePath = dir
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
const id = "https://example.com"
|
||||
dir, err := dlcache.New(id)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
exp := filepath.Join(dlcache.BasePath, sha1sum(id))
|
||||
if dir != exp {
|
||||
t.Errorf("Expected %s, got %s", exp, dir)
|
||||
}
|
||||
|
||||
fi, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
t.Errorf("stat: expected no error, got %s", err)
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
t.Errorf("Expected cache item to be a directory")
|
||||
}
|
||||
|
||||
dir2, ok := dlcache.Get(id)
|
||||
if !ok {
|
||||
t.Errorf("Expected Get() to return valid value")
|
||||
}
|
||||
if dir2 != dir {
|
||||
t.Errorf("Expected %s from Get(), got %s", dir, dir2)
|
||||
}
|
||||
}
|
||||
|
||||
func sha1sum(id string) string {
|
||||
h := sha1.New()
|
||||
_, _ = io.WriteString(h, id)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
@@ -1,32 +1,62 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 overrides
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/internal/cpu"
|
||||
"go.arsenm.dev/lure/internal/db"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type Opts struct {
|
||||
Name string
|
||||
Overrides bool
|
||||
LikeDistros bool
|
||||
Name string
|
||||
Overrides bool
|
||||
LikeDistros bool
|
||||
Languages []string
|
||||
LanguageTags []language.Tag
|
||||
}
|
||||
|
||||
var DefaultOpts = &Opts{
|
||||
Overrides: true,
|
||||
LikeDistros: true,
|
||||
Languages: []string{"en"},
|
||||
}
|
||||
|
||||
// Resolve generates a slice of possible override names in the order that they should be checked
|
||||
func Resolve(info *distro.OSRelease, opts *Opts) []string {
|
||||
func Resolve(info *distro.OSRelease, opts *Opts) ([]string, error) {
|
||||
if opts == nil {
|
||||
opts = DefaultOpts
|
||||
}
|
||||
|
||||
if !opts.Overrides {
|
||||
return []string{opts.Name}
|
||||
return []string{opts.Name}, nil
|
||||
}
|
||||
|
||||
langs, err := parseLangs(opts.Languages, opts.LanguageTags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
architectures := []string{runtime.GOARCH}
|
||||
@@ -71,7 +101,22 @@ func Resolve(info *distro.OSRelease, opts *Opts) []string {
|
||||
out[index] = strings.ReplaceAll(item, "-", "_")
|
||||
}
|
||||
|
||||
return out
|
||||
if len(langs) > 0 {
|
||||
tmp := out
|
||||
out = make([]string, 0, len(tmp)+(len(tmp)*len(langs)))
|
||||
for _, lang := range langs {
|
||||
for _, val := range tmp {
|
||||
if val == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, val+"_"+lang)
|
||||
}
|
||||
}
|
||||
out = append(out, tmp...)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (o *Opts) WithName(name string) *Opts {
|
||||
@@ -97,3 +142,94 @@ func (o *Opts) WithLikeDistros(v bool) *Opts {
|
||||
out.LikeDistros = v
|
||||
return out
|
||||
}
|
||||
|
||||
func (o *Opts) WithLanguages(langs []string) *Opts {
|
||||
out := &Opts{}
|
||||
*out = *o
|
||||
|
||||
out.Languages = langs
|
||||
return out
|
||||
}
|
||||
|
||||
func (o *Opts) WithLanguageTags(langs []string) *Opts {
|
||||
out := &Opts{}
|
||||
*out = *o
|
||||
|
||||
out.Languages = langs
|
||||
return out
|
||||
}
|
||||
|
||||
// ResolvedPackage is a LURE package after its overrides
|
||||
// have been resolved
|
||||
type ResolvedPackage struct {
|
||||
Name string `sh:"name"`
|
||||
Version string `sh:"version"`
|
||||
Release int `sh:"release"`
|
||||
Epoch uint `sh:"epoch"`
|
||||
Description string `db:"description"`
|
||||
Homepage string `db:"homepage"`
|
||||
Maintainer string `db:"maintainer"`
|
||||
Architectures []string `sh:"architectures"`
|
||||
Licenses []string `sh:"license"`
|
||||
Provides []string `sh:"provides"`
|
||||
Conflicts []string `sh:"conflicts"`
|
||||
Replaces []string `sh:"replaces"`
|
||||
Depends []string `sh:"deps"`
|
||||
BuildDepends []string `sh:"build_deps"`
|
||||
}
|
||||
|
||||
func ResolvePackage(pkg *db.Package, overrides []string) *ResolvedPackage {
|
||||
out := &ResolvedPackage{}
|
||||
outVal := reflect.ValueOf(out).Elem()
|
||||
pkgVal := reflect.ValueOf(pkg).Elem()
|
||||
|
||||
for i := 0; i < outVal.NumField(); i++ {
|
||||
fieldVal := outVal.Field(i)
|
||||
fieldType := fieldVal.Type()
|
||||
pkgFieldVal := pkgVal.FieldByName(outVal.Type().Field(i).Name)
|
||||
pkgFieldType := pkgFieldVal.Type()
|
||||
|
||||
if strings.HasPrefix(pkgFieldType.String(), "db.JSON") {
|
||||
pkgFieldVal = pkgFieldVal.FieldByName("Val")
|
||||
pkgFieldType = pkgFieldVal.Type()
|
||||
}
|
||||
|
||||
if pkgFieldType.AssignableTo(fieldType) {
|
||||
fieldVal.Set(pkgFieldVal)
|
||||
continue
|
||||
}
|
||||
|
||||
if pkgFieldVal.Kind() == reflect.Map && pkgFieldType.Elem().AssignableTo(fieldType) {
|
||||
for _, override := range overrides {
|
||||
overrideVal := pkgFieldVal.MapIndex(reflect.ValueOf(override))
|
||||
if !overrideVal.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldVal.Set(overrideVal)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
|
||||
out := make([]string, len(tags)+len(langs))
|
||||
for i, tag := range tags {
|
||||
base, _ := tag.Base()
|
||||
out[i] = base.String()
|
||||
}
|
||||
for i, lang := range langs {
|
||||
tag, err := language.Parse(lang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base, _ := tag.Base()
|
||||
out[len(tags)+i] = base.String()
|
||||
}
|
||||
slices.Sort(out)
|
||||
out = slices.Compact(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 overrides_test
|
||||
|
||||
import (
|
||||
@@ -6,6 +24,7 @@ import (
|
||||
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/internal/overrides"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var info = &distro.OSRelease{
|
||||
@@ -14,9 +33,19 @@ var info = &distro.OSRelease{
|
||||
}
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
names := overrides.Resolve(info, nil)
|
||||
names, err := overrides.Resolve(info, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"amd64_centos_en",
|
||||
"centos_en",
|
||||
"amd64_rhel_en",
|
||||
"rhel_en",
|
||||
"amd64_fedora_en",
|
||||
"fedora_en",
|
||||
"amd64_en",
|
||||
"amd64_centos",
|
||||
"centos",
|
||||
"amd64_rhel",
|
||||
@@ -33,11 +62,14 @@ func TestResolve(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveName(t *testing.T) {
|
||||
names := overrides.Resolve(info, &overrides.Opts{
|
||||
names, err := overrides.Resolve(info, &overrides.Opts{
|
||||
Name: "deps",
|
||||
Overrides: true,
|
||||
LikeDistros: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"deps_amd64_centos",
|
||||
@@ -56,10 +88,13 @@ func TestResolveName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveNoLikeDistros(t *testing.T) {
|
||||
names := overrides.Resolve(info, &overrides.Opts{
|
||||
names, err := overrides.Resolve(info, &overrides.Opts{
|
||||
Overrides: true,
|
||||
LikeDistros: false,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"amd64_centos",
|
||||
@@ -74,11 +109,14 @@ func TestResolveNoLikeDistros(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveNoOverrides(t *testing.T) {
|
||||
names := overrides.Resolve(info, &overrides.Opts{
|
||||
names, err := overrides.Resolve(info, &overrides.Opts{
|
||||
Name: "deps",
|
||||
Overrides: false,
|
||||
LikeDistros: false,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"deps"}
|
||||
|
||||
@@ -86,3 +124,31 @@ func TestResolveNoOverrides(t *testing.T) {
|
||||
t.Errorf("expected %v, got %v", expected, names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveLangs(t *testing.T) {
|
||||
names, err := overrides.Resolve(info, &overrides.Opts{
|
||||
Overrides: true,
|
||||
Languages: []string{"ru_RU", "en", "en_US"},
|
||||
LanguageTags: []language.Tag{language.BritishEnglish},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"amd64_centos_en",
|
||||
"centos_en",
|
||||
"amd64_en",
|
||||
"amd64_centos_ru",
|
||||
"centos_ru",
|
||||
"amd64_ru",
|
||||
"amd64_centos",
|
||||
"centos",
|
||||
"amd64",
|
||||
"",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(names, expected) {
|
||||
t.Errorf("expected %v, got %v", expected, names)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 pager
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 pager
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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_test
|
||||
|
||||
import (
|
||||
@@ -6,24 +24,18 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.arsenm.dev/lure/internal/db"
|
||||
"go.arsenm.dev/lure/internal/repos"
|
||||
"go.arsenm.dev/lure/internal/types"
|
||||
)
|
||||
|
||||
func TestFindPkgs(t *testing.T) {
|
||||
gdb, err := sqlx.Open("sqlite", ":memory:")
|
||||
gdb, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
|
||||
err = db.Init(gdb)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
setCfgDirs(t)
|
||||
defer removeCacheDir(t)
|
||||
|
||||
@@ -69,39 +81,40 @@ func TestFindPkgs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindPkgsEmpty(t *testing.T) {
|
||||
gdb, err := sqlx.Open("sqlite", ":memory:")
|
||||
gdb, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
|
||||
err = db.Init(gdb)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
setCfgDirs(t)
|
||||
defer removeCacheDir(t)
|
||||
|
||||
err = db.InsertPackage(gdb, db.Package{
|
||||
Name: "test1",
|
||||
Repository: "default",
|
||||
Version: "0.0.1",
|
||||
Release: 1,
|
||||
Description: "Test package 1",
|
||||
Provides: db.NewJSON([]string{""}),
|
||||
Name: "test1",
|
||||
Repository: "default",
|
||||
Version: "0.0.1",
|
||||
Release: 1,
|
||||
Description: db.NewJSON(map[string]string{
|
||||
"en": "Test package 1",
|
||||
"ru": "Проверочный пакет 1",
|
||||
}),
|
||||
Provides: db.NewJSON([]string{""}),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(gdb, db.Package{
|
||||
Name: "test2",
|
||||
Repository: "default",
|
||||
Version: "0.0.1",
|
||||
Release: 1,
|
||||
Description: "Test package 2",
|
||||
Provides: db.NewJSON([]string{"test"}),
|
||||
Name: "test2",
|
||||
Repository: "default",
|
||||
Version: "0.0.1",
|
||||
Release: 1,
|
||||
Description: db.NewJSON(map[string]string{
|
||||
"en": "Test package 2",
|
||||
"ru": "Проверочный пакет 2",
|
||||
}),
|
||||
Provides: db.NewJSON([]string{"test"}),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 (
|
||||
@@ -7,6 +25,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-billy/v5"
|
||||
@@ -18,7 +37,6 @@ import (
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/download"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
"go.arsenm.dev/lure/internal/db"
|
||||
"go.arsenm.dev/lure/internal/shutils"
|
||||
@@ -103,13 +121,9 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(repoURL.Scheme, "git+") {
|
||||
repoURL.Scheme = "git+" + repoURL.Scheme
|
||||
}
|
||||
|
||||
err = download.Get(ctx, download.GetOptions{
|
||||
SourceURL: repoURL.String(),
|
||||
Destination: repoDir,
|
||||
_, err = git.PlainCloneContext(ctx, repoDir, false, &git.CloneOptions{
|
||||
URL: repoURL.String(),
|
||||
Progress: os.Stderr,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -271,6 +285,9 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -338,6 +355,9 @@ func processRepoFull(ctx context.Context, repo types.Repo, repoDir string, gdb *
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -378,20 +398,34 @@ func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runn
|
||||
return d.DecodeVars(pkg)
|
||||
}
|
||||
|
||||
var overridable = map[string]string{
|
||||
"deps": "Depends",
|
||||
"build_deps": "BuildDepends",
|
||||
"desc": "Description",
|
||||
"homepage": "Homepage",
|
||||
"maintainer": "Maintainer",
|
||||
}
|
||||
|
||||
func resolveOverrides(runner *interp.Runner, pkg *db.Package) {
|
||||
pkgVal := reflect.ValueOf(pkg).Elem()
|
||||
for name, val := range runner.Vars {
|
||||
if strings.HasPrefix(name, "deps") {
|
||||
override := strings.TrimPrefix(name, "deps")
|
||||
override = strings.TrimPrefix(override, "_")
|
||||
for prefix, field := range overridable {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
override := strings.TrimPrefix(name, prefix)
|
||||
override = strings.TrimPrefix(override, "_")
|
||||
|
||||
pkg.Depends.Val[override] = val.List
|
||||
} else if strings.HasPrefix(name, "build_deps") {
|
||||
override := strings.TrimPrefix(name, "build_deps")
|
||||
override = strings.TrimPrefix(override, "_")
|
||||
field := pkgVal.FieldByName(field)
|
||||
varVal := field.FieldByName("Val")
|
||||
varType := varVal.Type()
|
||||
|
||||
pkg.BuildDepends.Val[override] = val.List
|
||||
} else {
|
||||
continue
|
||||
switch varType.Elem().String() {
|
||||
case "[]string":
|
||||
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.List))
|
||||
case "string":
|
||||
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.Str))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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_test
|
||||
|
||||
import (
|
||||
@@ -6,7 +24,6 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
"go.arsenm.dev/lure/internal/db"
|
||||
"go.arsenm.dev/lure/internal/repos"
|
||||
@@ -48,17 +65,12 @@ func removeCacheDir(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPull(t *testing.T) {
|
||||
gdb, err := sqlx.Open("sqlite", ":memory:")
|
||||
gdb, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
|
||||
err = db.Init(gdb)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
setCfgDirs(t)
|
||||
defer removeCacheDir(t)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -182,7 +182,11 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
|
||||
}
|
||||
|
||||
func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
||||
names := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, fnName := range names {
|
||||
fn, ok := d.runner.Funcs[fnName]
|
||||
if ok {
|
||||
@@ -195,7 +199,11 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
||||
// getVar gets a variable based on its name, taking into account
|
||||
// override variables and nameref variables.
|
||||
func (d *Decoder) getVar(name string) *expand.Variable {
|
||||
names := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, varName := range names {
|
||||
val, ok := d.runner.Vars[varName]
|
||||
if ok {
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 decoder_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 shutils_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -46,8 +46,8 @@ func (NopRWC) Read([]byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (NopRWC) Write([]byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
func (NopRWC) Write(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (NopRWC) Close() error {
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 shutils_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 types
|
||||
|
||||
// Config represents the LURE configuration file
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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 types
|
||||
|
||||
// RepoConfig represents a LURE repo's lure-repo.toml file.
|
||||
|
||||
2
list.go
2
list.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
38
main.go
38
main.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -20,6 +20,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -33,12 +34,26 @@ import (
|
||||
"go.arsenm.dev/lure/internal/config"
|
||||
"go.arsenm.dev/lure/internal/db"
|
||||
"go.arsenm.dev/lure/manager"
|
||||
"go.arsenm.dev/translate"
|
||||
)
|
||||
|
||||
//go:generate scripts/gen-version.sh
|
||||
|
||||
//go:embed translations
|
||||
var translationFS embed.FS
|
||||
|
||||
var translator translate.Translator
|
||||
|
||||
func init() {
|
||||
log.Logger = logger.NewCLI(os.Stderr)
|
||||
logger := logger.NewCLI(os.Stderr)
|
||||
|
||||
t, err := translate.NewFromFS(translationFS)
|
||||
if err != nil {
|
||||
logger.Fatal("Error creating new translator").Err(err).Send()
|
||||
}
|
||||
translator = t
|
||||
|
||||
log.Logger = translate.NewLogger(logger, t, config.Language)
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -65,6 +80,13 @@ func main() {
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Name: "install",
|
||||
Usage: "Install a new package",
|
||||
Aliases: []string{"in"},
|
||||
@@ -78,6 +100,13 @@ func main() {
|
||||
Action: removeCmd,
|
||||
},
|
||||
{
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Name: "upgrade",
|
||||
Usage: "Upgrade all installed packages",
|
||||
Aliases: []string{"up"},
|
||||
@@ -115,6 +144,11 @@ func main() {
|
||||
Value: "lure.sh",
|
||||
Usage: "Path to the build script",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Name: "build",
|
||||
Usage: "Build a local package",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -112,15 +112,8 @@ func (a *APK) UpgradeAll(opts *Opts) error {
|
||||
}
|
||||
|
||||
func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(a.rootCmd), "apk", "list", "-I")
|
||||
} else {
|
||||
cmd = exec.Command("apk", "list", "-I")
|
||||
}
|
||||
cmd := exec.Command("apk", "list", "-I")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -104,15 +104,8 @@ func (a *APT) UpgradeAll(opts *Opts) error {
|
||||
}
|
||||
|
||||
func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(a.rootCmd), "dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||
} else {
|
||||
cmd = exec.Command("dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||
}
|
||||
cmd := exec.Command("dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -111,15 +111,8 @@ func (d *DNF) UpgradeAll(opts *Opts) error {
|
||||
}
|
||||
|
||||
func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(d.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
} else {
|
||||
cmd = exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
}
|
||||
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -111,15 +111,8 @@ func (p *Pacman) UpgradeAll(opts *Opts) error {
|
||||
}
|
||||
|
||||
func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(p.rootCmd), "pacman", "-Q")
|
||||
} else {
|
||||
cmd = exec.Command("pacman", "-Q")
|
||||
}
|
||||
cmd := exec.Command("pacman", "-Q")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -111,15 +111,8 @@ func (y *YUM) UpgradeAll(opts *Opts) error {
|
||||
}
|
||||
|
||||
func (y *YUM) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(y.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
} else {
|
||||
cmd = exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
}
|
||||
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -111,15 +111,8 @@ func (z *Zypper) UpgradeAll(opts *Opts) error {
|
||||
}
|
||||
|
||||
func (z *Zypper) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(z.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
} else {
|
||||
cmd = exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
}
|
||||
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
||||
2
repo.go
2
repo.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
143
translations/lure.en.toml
Normal file
143
translations/lure.en.toml
Normal file
@@ -0,0 +1,143 @@
|
||||
[[translation]]
|
||||
id = 1228660974
|
||||
value = 'Pulling repository'
|
||||
|
||||
[[translation]]
|
||||
id = 2779805870
|
||||
value = 'Repository up to date'
|
||||
|
||||
[[translation]]
|
||||
id = 1433222829
|
||||
value = 'Would you like to view the build script for'
|
||||
|
||||
[[translation]]
|
||||
id = 2470847050
|
||||
value = 'Failed to prompt user to view build script'
|
||||
|
||||
[[translation]]
|
||||
id = 855659503
|
||||
value = 'Would you still like to continue?'
|
||||
|
||||
[[translation]]
|
||||
id = 1997041569
|
||||
value = 'User chose not to continue after reading script'
|
||||
|
||||
[[translation]]
|
||||
id = 2347700990
|
||||
value = 'Building package'
|
||||
|
||||
[[translation]]
|
||||
id = 2105058868
|
||||
value = 'Downloading sources'
|
||||
|
||||
[[translation]]
|
||||
id = 1884485082
|
||||
value = 'Downloading source'
|
||||
|
||||
[[translation]]
|
||||
id = 1519177982
|
||||
value = 'Error building package'
|
||||
|
||||
[[translation]]
|
||||
id = 2125220917
|
||||
value = 'Choose which package(s) to install'
|
||||
|
||||
[[translation]]
|
||||
id = 812531604
|
||||
value = 'Error prompting for choice of package'
|
||||
|
||||
[[translation]]
|
||||
id = 1040982801
|
||||
value = 'Updating version'
|
||||
|
||||
[[translation]]
|
||||
id = 1014897988
|
||||
value = 'Remove build dependencies?'
|
||||
|
||||
[[translation]]
|
||||
id = 2205430948
|
||||
value = 'Installing build dependencies'
|
||||
|
||||
[[translation]]
|
||||
id = 2522710805
|
||||
value = 'Installing dependencies'
|
||||
|
||||
[[translation]]
|
||||
id = 3602138206
|
||||
value = 'Error installing package'
|
||||
|
||||
[[translation]]
|
||||
id = 2235794125
|
||||
value = 'Would you like to remove build dependencies?'
|
||||
|
||||
[[translation]]
|
||||
id = 2562049386
|
||||
value = "Your system's CPU architecture doesn't match this package. Do you want to build anyway?"
|
||||
|
||||
[[translation]]
|
||||
id = 4006393493
|
||||
value = 'The checksums array must be the same length as sources'
|
||||
|
||||
[[translation]]
|
||||
id = 3759891273
|
||||
value = 'The package() function is required'
|
||||
|
||||
[[translation]]
|
||||
id = 1057080231
|
||||
value = 'Executing package()'
|
||||
|
||||
[[translation]]
|
||||
id = 2687735200
|
||||
value = 'Executing prepare()'
|
||||
|
||||
[[translation]]
|
||||
id = 535572372
|
||||
value = 'Executing version()'
|
||||
|
||||
[[translation]]
|
||||
id = 436644691
|
||||
value = 'Executing build()'
|
||||
|
||||
[[translation]]
|
||||
id = 1393316459
|
||||
value = 'This package is already installed'
|
||||
|
||||
[[translation]]
|
||||
id = 1267660189
|
||||
value = 'Source can be updated, updating if required'
|
||||
|
||||
[[translation]]
|
||||
id = 21753247
|
||||
value = 'Source found in cache, linked to destination'
|
||||
|
||||
[[translation]]
|
||||
id = 257354570
|
||||
value = 'Compressing package'
|
||||
|
||||
[[translation]]
|
||||
id = 2952487371
|
||||
value = 'Building package metadata'
|
||||
|
||||
[[translation]]
|
||||
id = 1579384326
|
||||
value = 'name'
|
||||
|
||||
[[translation]]
|
||||
id = 3206337475
|
||||
value = 'version'
|
||||
|
||||
[[translation]]
|
||||
id = 1810056261
|
||||
value = 'new'
|
||||
|
||||
[[translation]]
|
||||
id = 1602912115
|
||||
value = 'source'
|
||||
|
||||
[[translation]]
|
||||
id = 2363381545
|
||||
value = 'type'
|
||||
|
||||
[[translation]]
|
||||
id = 3419504365
|
||||
value = 'downloader'
|
||||
139
translations/lure.ru.toml
Normal file
139
translations/lure.ru.toml
Normal file
@@ -0,0 +1,139 @@
|
||||
[[translation]]
|
||||
id = 1228660974
|
||||
value = 'Скачивание репозитория'
|
||||
|
||||
[[translation]]
|
||||
id = 2779805870
|
||||
value = 'Репозиторий уже обновлен'
|
||||
|
||||
[[translation]]
|
||||
id = 1433222829
|
||||
value = 'Показать скрипт для пакета'
|
||||
|
||||
[[translation]]
|
||||
id = 2470847050
|
||||
value = 'Не удалось предложить просмотреть скрипт'
|
||||
|
||||
[[translation]]
|
||||
id = 855659503
|
||||
value = 'Продолжить?'
|
||||
|
||||
[[translation]]
|
||||
id = 1997041569
|
||||
value = 'Пользователь решил не продолжать после просмотра скрипта'
|
||||
|
||||
[[translation]]
|
||||
id = 2347700990
|
||||
value = 'Сборка пакета'
|
||||
|
||||
[[translation]]
|
||||
id = 2105058868
|
||||
value = 'Скачивание файлов'
|
||||
|
||||
[[translation]]
|
||||
id = 1884485082
|
||||
value = 'Скачивание источника'
|
||||
|
||||
[[translation]]
|
||||
id = 1519177982
|
||||
value = 'Ошибка при сборке пакета'
|
||||
|
||||
[[translation]]
|
||||
id = 2125220917
|
||||
value = 'Выберите, какие пакеты установить'
|
||||
|
||||
[[translation]]
|
||||
id = 812531604
|
||||
value = 'Ошибка при запросе выбора пакета'
|
||||
|
||||
[[translation]]
|
||||
id = 1040982801
|
||||
value = 'Обновление версии'
|
||||
|
||||
[[translation]]
|
||||
id = 2235794125
|
||||
value = 'Удалить зависимости сборки?'
|
||||
|
||||
[[translation]]
|
||||
id = 2205430948
|
||||
value = 'Установка зависимостей сборки'
|
||||
|
||||
[[translation]]
|
||||
id = 2522710805
|
||||
value = 'Установка зависимостей'
|
||||
|
||||
[[translation]]
|
||||
id = 3602138206
|
||||
value = 'Ошибка при установке пакета'
|
||||
|
||||
[[translation]]
|
||||
id = 1057080231
|
||||
value = 'Вызов функции package()'
|
||||
|
||||
[[translation]]
|
||||
id = 2687735200
|
||||
value = 'Вызов функции prepare()'
|
||||
|
||||
[[translation]]
|
||||
id = 535572372
|
||||
value = 'Вызов функции version()'
|
||||
|
||||
[[translation]]
|
||||
id = 436644691
|
||||
value = 'Вызов функции build()'
|
||||
|
||||
[[translation]]
|
||||
id = 2562049386
|
||||
value = "Архитектура процессора вашей системы не соответствует этому пакету. Продолжать несмотря на это?"
|
||||
|
||||
[[translation]]
|
||||
id = 3759891273
|
||||
value = 'Функция package() необходима'
|
||||
|
||||
[[translation]]
|
||||
id = 4006393493
|
||||
value = 'Массив checksums должен быть той же длины, что и sources'
|
||||
|
||||
[[translation]]
|
||||
id = 1393316459
|
||||
value = 'Этот пакет уже установлен'
|
||||
|
||||
[[translation]]
|
||||
id = 1267660189
|
||||
value = 'Источник может быть обновлен, если требуется, обновляем'
|
||||
|
||||
[[translation]]
|
||||
id = 21753247
|
||||
value = 'Источник найден в кэше'
|
||||
|
||||
[[translation]]
|
||||
id = 257354570
|
||||
value = 'Сжатие пакета'
|
||||
|
||||
[[translation]]
|
||||
id = 2952487371
|
||||
value = 'Создание метаданных пакета'
|
||||
|
||||
[[translation]]
|
||||
id = 1579384326
|
||||
value = 'название'
|
||||
|
||||
[[translation]]
|
||||
id = 3206337475
|
||||
value = 'версия'
|
||||
|
||||
[[translation]]
|
||||
id = 1810056261
|
||||
value = 'новая'
|
||||
|
||||
[[translation]]
|
||||
id = 1602912115
|
||||
value = 'источник'
|
||||
|
||||
[[translation]]
|
||||
id = 2363381545
|
||||
value = 'вид'
|
||||
|
||||
[[translation]]
|
||||
id = 3419504365
|
||||
value = 'протокол-скачивание'
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
@@ -55,7 +55,7 @@ func upgradeCmd(c *cli.Context) error {
|
||||
}
|
||||
|
||||
if len(updates) > 0 {
|
||||
installPkgs(c.Context, updates, nil, mgr)
|
||||
installPkgs(c.Context, updates, nil, mgr, c.Bool("clean"))
|
||||
} else {
|
||||
log.Info("There is nothing to do.").Send()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
* Copyright (C) 2023 Arsen Musayelyan
|
||||
*
|
||||
* 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
|
||||
|
||||
Reference in New Issue
Block a user