Compare commits
No commits in common. "v0.0.6" and "master" have entirely different histories.
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
liberapay: lure
|
@ -1,12 +1,13 @@
|
|||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
- go generate
|
|
||||||
builds:
|
builds:
|
||||||
- id: lure
|
- id: lure
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
binary: lure
|
binary: lure
|
||||||
|
ldflags:
|
||||||
|
- -X go.elara.ws/lure/internal/config.Version={{.Version}}
|
||||||
goos:
|
goos:
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
@ -16,21 +17,30 @@ builds:
|
|||||||
- arm
|
- arm
|
||||||
- riscv64
|
- riscv64
|
||||||
archives:
|
archives:
|
||||||
- replacements:
|
- name_template: >-
|
||||||
386: i386
|
{{- .ProjectName}}-
|
||||||
amd64: x86_64
|
{{- .Version}}-
|
||||||
arm64: aarch64
|
{{- .Os}}-
|
||||||
|
{{- if .Arch | eq "amd64"}}x86_64
|
||||||
|
{{- else if .Arch | eq "386"}}i386
|
||||||
|
{{- else if .Arch | eq "arm64"}}aarch64
|
||||||
|
{{- else }}{{ .Arch }}{{ end -}}
|
||||||
|
files:
|
||||||
|
- scripts/completion/*
|
||||||
nfpms:
|
nfpms:
|
||||||
- id: lure
|
- id: lure
|
||||||
package_name: linux-user-repository
|
package_name: linux-user-repository
|
||||||
file_name_template: '{{.PackageName}}-{{.Version}}-{{.Os}}-{{.Arch}}'
|
file_name_template: >-
|
||||||
|
{{- .PackageName}}-
|
||||||
|
{{- .Version}}-
|
||||||
|
{{- .Os}}-
|
||||||
|
{{- if .Arch | eq "amd64"}}x86_64
|
||||||
|
{{- else if .Arch | eq "386"}}i386
|
||||||
|
{{- else if .Arch | eq "arm64"}}aarch64
|
||||||
|
{{- else }}{{ .Arch }}{{ end -}}
|
||||||
description: "Linux User REpository"
|
description: "Linux User REpository"
|
||||||
replacements:
|
homepage: 'https://lure.sh'
|
||||||
386: i386
|
maintainer: 'Elara Musayelyan <elara@elara.ws>'
|
||||||
amd64: x86_64
|
|
||||||
arm64: aarch64
|
|
||||||
homepage: 'https://gitea.arsenm.dev/Arsen6331/lure'
|
|
||||||
maintainer: 'Arsen Musayelyan <arsen@arsenm.dev>'
|
|
||||||
license: GPLv3
|
license: GPLv3
|
||||||
formats:
|
formats:
|
||||||
- apk
|
- apk
|
||||||
@ -38,44 +48,48 @@ nfpms:
|
|||||||
- rpm
|
- rpm
|
||||||
- archlinux
|
- archlinux
|
||||||
provides:
|
provides:
|
||||||
- lure
|
- linux-user-repository
|
||||||
conflicts:
|
conflicts:
|
||||||
- lure
|
- linux-user-repository
|
||||||
|
recommends:
|
||||||
|
- aria2
|
||||||
contents:
|
contents:
|
||||||
- src: scripts/completion/bash
|
- src: scripts/completion/bash
|
||||||
dst: /usr/share/bash-completion/completions/lure
|
dst: /usr/share/bash-completion/completions/lure
|
||||||
- src: scripts/completion/zsh
|
- src: scripts/completion/zsh
|
||||||
dst: /usr/share/zsh/site-functions/_lure
|
dst: /usr/share/zsh/site-functions/_lure
|
||||||
aurs:
|
aurs:
|
||||||
- name: lure-bin
|
- name: linux-user-repository-bin
|
||||||
homepage: 'https://gitea.arsenm.dev/Arsen6331/lure'
|
homepage: 'https://lure.sh'
|
||||||
description: "Linux User REpository"
|
description: "Linux User REpository"
|
||||||
maintainers:
|
maintainers:
|
||||||
- 'Arsen Musayelyan <arsen@arsenm.dev>'
|
- 'Elara Musayelyan <elara@elara.ws>'
|
||||||
license: GPLv3
|
license: GPLv3
|
||||||
private_key: '{{ .Env.AUR_KEY }}'
|
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:
|
provides:
|
||||||
- lure
|
- linux-user-repository
|
||||||
conflicts:
|
conflicts:
|
||||||
- lure
|
- linux-user-repository
|
||||||
depends:
|
depends:
|
||||||
- sudo
|
- sudo
|
||||||
- pacman
|
- pacman
|
||||||
|
optdepends:
|
||||||
|
- 'aria2: for downloading torrent sources'
|
||||||
package: |-
|
package: |-
|
||||||
# binaries
|
# binaries
|
||||||
install -Dm755 ./lure "${pkgdir}/usr/bin/lure"
|
install -Dm755 ./lure "${pkgdir}/usr/bin/lure"
|
||||||
|
|
||||||
# completions
|
# completions
|
||||||
install -Dm755 ./scripts/completion/bash /usr/share/bash-completion/completions/lure
|
install -Dm755 ./scripts/completion/bash ${pkgdir}/usr/share/bash-completion/completions/lure
|
||||||
install -Dm755 ./scripts/completion/zsh /usr/share/zsh/site-functions/_lure
|
install -Dm755 ./scripts/completion/zsh ${pkgdir}/usr/share/zsh/site-functions/_lure
|
||||||
release:
|
release:
|
||||||
gitea:
|
gitea:
|
||||||
owner: Arsen6331
|
owner: lure
|
||||||
name: lure
|
name: lure
|
||||||
gitea_urls:
|
gitea_urls:
|
||||||
api: 'https://gitea.arsenm.dev/api/v1/'
|
api: 'https://gitea.elara.ws/api/v1/'
|
||||||
download: 'https://gitea.arsenm.dev'
|
download: 'https://gitea.elara.ws'
|
||||||
skip_tls_verify: false
|
skip_tls_verify: false
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
platform: linux/amd64
|
||||||
pipeline:
|
pipeline:
|
||||||
release:
|
release:
|
||||||
image: goreleaser/goreleaser
|
image: goreleaser/goreleaser
|
||||||
|
10
Makefile
10
Makefile
@ -1,7 +1,8 @@
|
|||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
|
GIT_VERSION = $(shell git describe --tags )
|
||||||
|
|
||||||
lure: version.txt
|
lure:
|
||||||
go build
|
CGO_ENABLED=0 go build -ldflags="-X 'go.elara.ws/lure/internal/config.Version=$(GIT_VERSION)'"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f lure
|
rm -f lure
|
||||||
@ -15,8 +16,5 @@ installmisc:
|
|||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f /usr/local/bin/lure
|
rm -f /usr/local/bin/lure
|
||||||
|
|
||||||
version.txt:
|
|
||||||
go generate ./...
|
|
||||||
|
|
||||||
.PHONY: install clean uninstall
|
.PHONY: install clean uninstall installmisc lure
|
35
README.md
35
README.md
@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
# LURE (Linux User REpository)
|
# LURE (Linux User REpository)
|
||||||
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/go.arsenm.dev/lure)](https://goreportcard.com/report/go.arsenm.dev/lure)
|
[![Go Report Card](https://goreportcard.com/badge/go.elara.ws/lure)](https://goreportcard.com/report/go.elara.ws/lure)
|
||||||
[![status-badge](https://ci.arsenm.dev/api/badges/Arsen6331/lure/status.svg)](https://ci.arsenm.dev/Arsen6331/lure)
|
[![status-badge](https://ci.elara.ws/api/badges/lure/lure/status.svg)](https://ci.elara.ws/lure/lure)
|
||||||
[![lure-bin AUR package](https://img.shields.io/aur/version/lure-bin?label=lure-bin&logo=archlinux)](https://aur.archlinux.org/packages/lure-bin/)
|
[![SWH](https://archive.softwareheritage.org/badge/origin/https://gitea.elara.ws/lure/lure.git/)](https://archive.softwareheritage.org/browse/origin/?origin_url=https://gitea.elara.ws/lure/lure.git)
|
||||||
|
[![linux-user-repository-bin AUR package](https://img.shields.io/aur/version/linux-user-repository-bin?label=linux-user-repository-bin&logo=archlinux)](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.
|
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.
|
||||||
|
|
||||||
@ -19,16 +20,16 @@ LURE is written in pure Go and has zero dependencies after building. The only th
|
|||||||
The LURE install script will automatically download and install the appropriate LURE package on your system. To use it, simply run the following command:
|
The LURE install script will automatically download and install the appropriate LURE package on your system. To use it, simply run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl https://www.arsenm.dev/lure.sh | bash
|
curl -fsSL lure.sh/install | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
**IMPORTANT**: This method is not recommended as it executes any code that is stored at that URL. In order to make sure nothing malicious is going to occur, download the script and inspect it before running.
|
**IMPORTANT**: This will download and run the script from https://lure.sh/install. Please look through any script you download from the internet (including this one) before running it.
|
||||||
|
|
||||||
### Packages
|
### Packages
|
||||||
|
|
||||||
Distro packages and binary archives are provided at the latest Gitea release: https://gitea.arsenm.dev/Arsen6331/lure/releases/latest
|
Distro packages and binary archives are provided at the latest Gitea release: https://gitea.elara.ws/lure/lure/releases/latest
|
||||||
|
|
||||||
LURE is also available on the AUR as [lure-bin](https://aur.archlinux.org/packages/lure-bin)
|
LURE is also available on the AUR as [linux-user-repository-bin](https://aur.archlinux.org/packages/linux-user-repository-bin)
|
||||||
|
|
||||||
### Building from source
|
### Building from source
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ sudo make install
|
|||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
Arch Linux's AUR is a very useful feature. It's one of the main reasons I and many others use Arch on most of their devices. However, Arch is not always a good choice. Whether you're running a server that needs to be stable over long periods of time, or you're a beginner and feel intimidated by Arch, there are many different reasons not to use it. Such useful functionality should not be restricted to only a single distro. That is what LURE is meant to solve.
|
LURE was created because packaging software for multiple Linux distros can be difficult and error-prone, and installing those packages can be a nightmare for users unless they're available in their distro's official repositories. It automates the process of building and installing unofficial packages.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -54,29 +55,23 @@ The documentation for LURE is in the [docs](docs) directory in this repo.
|
|||||||
|
|
||||||
## Web Interface
|
## Web Interface
|
||||||
|
|
||||||
LURE now has a web interface! It's open source, licensed under the AGPLv3 (https://gitea.arsenm.dev/Arsen6331/lure-web), and is available at https://lure.arsenm.dev.
|
LURE has an open source web interface, licensed under the AGPLv3 (https://gitea.elara.ws/lure/lure-web), and it's available at https://lure.sh/.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Repositories
|
## Repositories
|
||||||
|
|
||||||
Unlike the AUR, LURE supports third-party repositories. Also unlike the AUR, LURE's repos are single git repositories containing all the build scripts. Inside each LURE repo, there is a separate directory for each package, containing a `lure.sh` script, which is a PKGBUILD-like build script for LURE. The default repository is hosted on Github: https://github.com/Arsen6331/lure-repo, and information about its packages is displayed at https://lure.arsenm.dev/pkgs.
|
LURE's repos are git repositories that contain a directory for each package, with a `lure.sh` file inside. The `lure.sh` file tells LURE how to build the package and information about it. `lure.sh` scripts are similar to the AUR's PKGBUILD scripts.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Dependencies
|
## Acknowledgements
|
||||||
|
|
||||||
As mentioned before, LURE has zero dependencies after compilation. Thanks to the following projects for making this possible:
|
Thanks to the following projects for making LURE possible:
|
||||||
|
|
||||||
- https://github.com/mvdan/sh
|
- https://github.com/mvdan/sh
|
||||||
- https://github.com/go-git/go-git
|
- https://github.com/go-git/go-git
|
||||||
- https://github.com/mholt/archiver
|
- https://github.com/mholt/archiver
|
||||||
- https://github.com/goreleaser/nfpm
|
- https://github.com/goreleaser/nfpm
|
||||||
- https://github.com/charmbracelet/bubbletea
|
- https://github.com/charmbracelet/bubbletea
|
||||||
- https://gitlab.com/cznic/sqlite
|
- https://gitlab.com/cznic/sqlite
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Planned Features
|
|
||||||
|
|
||||||
- Automated docker-based testing tool
|
|
666
build.go
666
build.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -19,624 +19,82 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
_ "github.com/goreleaser/nfpm/v2/apk"
|
|
||||||
_ "github.com/goreleaser/nfpm/v2/arch"
|
|
||||||
_ "github.com/goreleaser/nfpm/v2/deb"
|
|
||||||
_ "github.com/goreleaser/nfpm/v2/rpm"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/exp/slices"
|
"lure.sh/lure/internal/config"
|
||||||
|
"lure.sh/lure/internal/osutils"
|
||||||
"github.com/goreleaser/nfpm/v2"
|
"lure.sh/lure/internal/types"
|
||||||
"github.com/goreleaser/nfpm/v2/files"
|
"lure.sh/lure/pkg/build"
|
||||||
"go.arsenm.dev/logger/log"
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
"go.arsenm.dev/lure/distro"
|
"lure.sh/lure/pkg/manager"
|
||||||
"go.arsenm.dev/lure/download"
|
"lure.sh/lure/pkg/repos"
|
||||||
"go.arsenm.dev/lure/internal/cliutils"
|
|
||||||
"go.arsenm.dev/lure/internal/config"
|
|
||||||
"go.arsenm.dev/lure/internal/cpu"
|
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
|
||||||
"go.arsenm.dev/lure/internal/shutils"
|
|
||||||
"go.arsenm.dev/lure/internal/shutils/decoder"
|
|
||||||
"go.arsenm.dev/lure/manager"
|
|
||||||
"mvdan.cc/sh/v3/expand"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuildVars represents the script variables required
|
var buildCmd = &cli.Command{
|
||||||
// to build a package
|
Name: "build",
|
||||||
type BuildVars struct {
|
Usage: "Build a local package",
|
||||||
Name string `sh:"name,required"`
|
Flags: []cli.Flag{
|
||||||
Version string `sh:"version,required"`
|
&cli.StringFlag{
|
||||||
Release int `sh:"release,required"`
|
Name: "script",
|
||||||
Epoch uint `sh:"epoch"`
|
Aliases: []string{"s"},
|
||||||
Description string `sh:"desc"`
|
Value: "lure.sh",
|
||||||
Homepage string `sh:"homepage"`
|
Usage: "Path to the build script",
|
||||||
Maintainer string `sh:"maintainer"`
|
|
||||||
Architectures []string `sh:"architectures"`
|
|
||||||
Licenses []string `sh:"license"`
|
|
||||||
Provides []string `sh:"provides"`
|
|
||||||
Conflicts []string `sh:"conflicts"`
|
|
||||||
Depends []string `sh:"deps"`
|
|
||||||
BuildDepends []string `sh:"build_deps"`
|
|
||||||
Replaces []string `sh:"replaces"`
|
|
||||||
Sources []string `sh:"sources"`
|
|
||||||
Checksums []string `sh:"checksums"`
|
|
||||||
Backup []string `sh:"backup"`
|
|
||||||
Scripts Scripts `sh:"scripts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Scripts struct {
|
|
||||||
PreInstall string `sh:"preinstall"`
|
|
||||||
PostInstall string `sh:"postinstall"`
|
|
||||||
PreRemove string `sh:"preremove"`
|
|
||||||
PostRemove string `sh:"postremove"`
|
|
||||||
PreUpgrade string `sh:"preupgrade"`
|
|
||||||
PostUpgrade string `sh:"postupgrade"`
|
|
||||||
PreTrans string `sh:"pretrans"`
|
|
||||||
PostTrans string `sh:"posttrans"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildCmd(c *cli.Context) error {
|
|
||||||
script := c.String("script")
|
|
||||||
|
|
||||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
mgr := manager.Detect()
|
|
||||||
if mgr == nil {
|
|
||||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgPaths, _, err := buildPackage(c.Context, script, mgr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error building package").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error getting working directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pkgPath := range pkgPaths {
|
|
||||||
name := filepath.Base(pkgPath)
|
|
||||||
err = os.Rename(pkgPath, filepath.Join(wd, name))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error moving the package").Err(err).Send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
info, err := distro.ParseOSRelease(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var distroChanged bool
|
|
||||||
if distID, ok := os.LookupEnv("LURE_DISTRO"); ok {
|
|
||||||
info.ID = distID
|
|
||||||
// Since the distro was overwritten, we don't know what the
|
|
||||||
// like distros are, so set to nil
|
|
||||||
info.Like = nil
|
|
||||||
distroChanged = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fl, err := os.Open(script)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fl.Close()
|
|
||||||
|
|
||||||
scriptDir := filepath.Dir(script)
|
|
||||||
env := genBuildEnv(info, scriptDir)
|
|
||||||
|
|
||||||
// The first pass is just used to get variable values and runs before
|
|
||||||
// the script is displayed, so it is restricted so as to prevent malicious
|
|
||||||
// code from executing.
|
|
||||||
runner, err := interp.New(
|
|
||||||
interp.Env(expand.ListEnviron(env...)),
|
|
||||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
|
||||||
interp.ExecHandler(rHelpers.ExecHandler(shutils.NopExec)),
|
|
||||||
interp.ReadDirHandler(shutils.RestrictedReadDir(scriptDir)),
|
|
||||||
interp.StatHandler(shutils.RestrictedStat(scriptDir)),
|
|
||||||
interp.OpenHandler(shutils.RestrictedOpen(scriptDir)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := decoder.New(info, runner)
|
|
||||||
|
|
||||||
// If distro was changed, the list of like distros
|
|
||||||
// no longer applies, so disable its use
|
|
||||||
if distroChanged {
|
|
||||||
dec.LikeDistros = false
|
|
||||||
}
|
|
||||||
|
|
||||||
var vars BuildVars
|
|
||||||
err = dec.DecodeVars(&vars)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cliutils.PromptViewScript(script, vars.Name, cfg.PagerStyle)
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !buildAnyway {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
|
|
||||||
|
|
||||||
// The second pass will be used to execute the actual functions,
|
|
||||||
// so it cannot be restricted. The script has already been displayed
|
|
||||||
// to the user by this point, so it should be safe
|
|
||||||
runner, err = interp.New(
|
|
||||||
interp.Env(expand.ListEnviron(env...)),
|
|
||||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
|
||||||
interp.ExecHandler(helpers.ExecHandler(nil)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dec = decoder.New(info, runner)
|
|
||||||
|
|
||||||
// If distro was changed, the list of like distros
|
|
||||||
// no longer applies, so disable its use
|
|
||||||
if distroChanged {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(srcdir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(pkgdir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
installed, err := mgr.ListInstalled(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buildDeps []string
|
|
||||||
for _, pkgName := range vars.BuildDepends {
|
|
||||||
if _, ok := installed[pkgName]; !ok {
|
|
||||||
buildDeps = append(buildDeps, pkgName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(buildDeps) > 0 {
|
|
||||||
found, notFound, err := repos.FindPkgs(gdb, buildDeps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Installing build dependencies").Send()
|
|
||||||
installPkgs(ctx, cliutils.FlattenPkgs(found, "install"), notFound, mgr)
|
|
||||||
}
|
|
||||||
|
|
||||||
var builtDeps, builtNames, repoDeps []string
|
|
||||||
if len(vars.Depends) > 0 {
|
|
||||||
log.Info("Installing dependencies").Send()
|
|
||||||
|
|
||||||
found, notFound, err := repos.FindPkgs(gdb, vars.Depends)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
scripts := getScriptPaths(cliutils.FlattenPkgs(found, "install"))
|
|
||||||
for _, script := range scripts {
|
|
||||||
pkgPaths, pkgNames, err := buildPackage(ctx, script, mgr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
builtDeps = append(builtDeps, pkgPaths...)
|
|
||||||
builtNames = append(builtNames, pkgNames...)
|
|
||||||
builtNames = append(builtNames, filepath.Base(filepath.Dir(script)))
|
|
||||||
}
|
|
||||||
repoDeps = notFound
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Downloading sources").Send()
|
|
||||||
|
|
||||||
err = getSources(ctx, srcdir, &vars)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setDirVars(ctx, runner, srcdir, pkgdir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fn, ok := dec.GetFunc("version")
|
|
||||||
if ok {
|
|
||||||
log.Info("Executing version()").Send()
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
|
|
||||||
err = fn(
|
|
||||||
ctx,
|
|
||||||
interp.Dir(srcdir),
|
|
||||||
interp.StdIO(os.Stdin, buf, os.Stderr),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newVer := strings.TrimSpace(buf.String())
|
|
||||||
err = setVersion(ctx, runner, newVer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
vars.Version = newVer
|
|
||||||
|
|
||||||
log.Info("Updating version").Str("new", newVer).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn, ok = dec.GetFunc("prepare")
|
|
||||||
if ok {
|
|
||||||
log.Info("Executing prepare()").Send()
|
|
||||||
|
|
||||||
err = fn(ctx, interp.Dir(srcdir))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn, ok = dec.GetFunc("build")
|
|
||||||
if ok {
|
|
||||||
log.Info("Executing build()").Send()
|
|
||||||
|
|
||||||
err = fn(ctx, interp.Dir(srcdir))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn, ok = dec.GetFunc("package")
|
|
||||||
if ok {
|
|
||||||
log.Info("Executing package()").Send()
|
|
||||||
|
|
||||||
err = fn(ctx, interp.Dir(srcdir))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Fatal("The package() function is required").Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
uniq(
|
|
||||||
&repoDeps,
|
|
||||||
&builtDeps,
|
|
||||||
&builtNames,
|
|
||||||
)
|
|
||||||
|
|
||||||
pkgInfo := &nfpm.Info{
|
|
||||||
Name: vars.Name,
|
|
||||||
Description: vars.Description,
|
|
||||||
Arch: cpu.Arch(),
|
|
||||||
Version: vars.Version,
|
|
||||||
Release: strconv.Itoa(vars.Release),
|
|
||||||
Homepage: vars.Homepage,
|
|
||||||
License: strings.Join(vars.Licenses, ", "),
|
|
||||||
Maintainer: vars.Maintainer,
|
|
||||||
Overridables: nfpm.Overridables{
|
|
||||||
Conflicts: vars.Conflicts,
|
|
||||||
Replaces: vars.Replaces,
|
|
||||||
Provides: vars.Provides,
|
|
||||||
Depends: append(repoDeps, builtNames...),
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringFlag{
|
||||||
|
Name: "package",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Usage: "Name of the package to build and its repo (example: default/go-bin)",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "clean",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Build package from scratch even if there's an already built package available",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
if vars.Epoch != 0 {
|
script := c.String("script")
|
||||||
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
|
if c.String("package") != "" {
|
||||||
}
|
script = filepath.Join(config.GetPaths(ctx).RepoDir, c.String("package"), "lure.sh")
|
||||||
|
}
|
||||||
|
|
||||||
setScripts(&vars, pkgInfo, filepath.Dir(script))
|
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
if slices.Contains(vars.Architectures, "all") {
|
mgr := manager.Detect()
|
||||||
pkgInfo.Arch = "all"
|
if mgr == nil {
|
||||||
}
|
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||||
|
}
|
||||||
|
|
||||||
contents := []*files.Content{}
|
pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{
|
||||||
filepath.Walk(pkgdir, func(path string, fi os.FileInfo, err error) error {
|
Script: script,
|
||||||
trimmed := strings.TrimPrefix(path, pkgdir)
|
Manager: mgr,
|
||||||
|
Clean: c.Bool("clean"),
|
||||||
|
Interactive: c.Bool("interactive"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error building package").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
if fi.IsDir() {
|
wd, err := os.Getwd()
|
||||||
f, err := os.Open(path)
|
if err != nil {
|
||||||
|
log.Fatal("Error getting working directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkgPath := range pkgPaths {
|
||||||
|
name := filepath.Base(pkgPath)
|
||||||
|
err = osutils.Move(pkgPath, filepath.Join(wd, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal("Error moving the package").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = f.Readdirnames(1)
|
|
||||||
if err != io.EOF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
contents = append(contents, &files.Content{
|
|
||||||
Source: path,
|
|
||||||
Destination: trimmed,
|
|
||||||
Type: "dir",
|
|
||||||
FileInfo: &files.ContentFileInfo{
|
|
||||||
MTime: fi.ModTime(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
f.Close()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.Mode()&os.ModeSymlink != 0 {
|
|
||||||
link, err := os.Readlink(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
link = strings.TrimPrefix(link, pkgdir)
|
|
||||||
|
|
||||||
contents = append(contents, &files.Content{
|
|
||||||
Source: link,
|
|
||||||
Destination: trimmed,
|
|
||||||
Type: "symlink",
|
|
||||||
FileInfo: &files.ContentFileInfo{
|
|
||||||
MTime: fi.ModTime(),
|
|
||||||
Mode: fi.Mode(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fileContent := &files.Content{
|
|
||||||
Source: path,
|
|
||||||
Destination: trimmed,
|
|
||||||
FileInfo: &files.ContentFileInfo{
|
|
||||||
MTime: fi.ModTime(),
|
|
||||||
Mode: fi.Mode(),
|
|
||||||
Size: fi.Size(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if slices.Contains(vars.Backup, trimmed) {
|
|
||||||
fileContent.Type = "config|noreplace"
|
|
||||||
}
|
|
||||||
|
|
||||||
contents = append(contents, fileContent)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
|
||||||
pkgInfo.Overridables.Contents = contents
|
|
||||||
|
|
||||||
pkgFormat := mgr.Format()
|
|
||||||
if format, ok := os.LookupEnv("LURE_PKG_FORMAT"); ok {
|
|
||||||
pkgFormat = format
|
|
||||||
}
|
|
||||||
|
|
||||||
packager, err := nfpm.Get(pkgFormat)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgName := packager.ConventionalFileName(pkgInfo)
|
|
||||||
pkgPath := filepath.Join(baseDir, pkgName)
|
|
||||||
|
|
||||||
pkgPaths := append(builtDeps, pkgPath)
|
|
||||||
pkgNames := append(builtNames, vars.Name)
|
|
||||||
|
|
||||||
pkgFile, err := os.Create(pkgPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = packager.Package(pkgInfo, pkgFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(buildDeps) > 0 {
|
|
||||||
removeBuildDeps, err := cliutils.YesNoPrompt("Would you like to remove build dependencies?", false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if removeBuildDeps {
|
|
||||||
err = mgr.Remove(
|
|
||||||
&manager.Opts{
|
|
||||||
AsRoot: true,
|
|
||||||
NoConfirm: true,
|
|
||||||
},
|
|
||||||
buildDeps...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uniq(&pkgPaths, &pkgNames)
|
|
||||||
|
|
||||||
return pkgPaths, pkgNames, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func genBuildEnv(info *distro.OSRelease, scriptdir string) []string {
|
|
||||||
env := os.Environ()
|
|
||||||
|
|
||||||
env = append(
|
|
||||||
env,
|
|
||||||
"DISTRO_NAME="+info.Name,
|
|
||||||
"DISTRO_PRETTY_NAME="+info.PrettyName,
|
|
||||||
"DISTRO_ID="+info.ID,
|
|
||||||
"DISTRO_VERSION_ID="+info.VersionID,
|
|
||||||
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
|
||||||
|
|
||||||
"ARCH="+cpu.Arch(),
|
|
||||||
"NCPU="+strconv.Itoa(runtime.NumCPU()),
|
|
||||||
|
|
||||||
"scriptdir="+scriptdir,
|
|
||||||
)
|
|
||||||
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSources(ctx context.Context, srcdir string, bv *BuildVars) error {
|
|
||||||
if len(bv.Sources) != len(bv.Checksums) {
|
|
||||||
log.Fatal("The checksums array must be the same length as sources")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, src := range bv.Sources {
|
|
||||||
opts := download.GetOptions{
|
|
||||||
SourceURL: src,
|
|
||||||
Destination: srcdir,
|
|
||||||
EncloseGit: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
|
|
||||||
checksum, err := hex.DecodeString(bv.Checksums[i])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
opts.SHA256Sum = checksum
|
|
||||||
}
|
|
||||||
|
|
||||||
err := download.Get(ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDirVars sets srcdir and pkgdir. It's a very hacky way of doing so,
|
|
||||||
// but setting the runner's Env and Vars fields doesn't seem to work.
|
|
||||||
func setDirVars(ctx context.Context, runner *interp.Runner, srcdir, pkgdir string) error {
|
|
||||||
cmd := "srcdir='" + srcdir + "'\npkgdir='" + pkgdir + "'\n"
|
|
||||||
fl, err := syntax.NewParser().Parse(strings.NewReader(cmd), "vars")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return runner.Run(ctx, fl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setScripts(vars *BuildVars, info *nfpm.Info, scriptDir string) {
|
|
||||||
if vars.Scripts.PreInstall != "" {
|
|
||||||
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
|
|
||||||
}
|
|
||||||
|
|
||||||
if vars.Scripts.PostInstall != "" {
|
|
||||||
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall)
|
|
||||||
}
|
|
||||||
|
|
||||||
if vars.Scripts.PreRemove != "" {
|
|
||||||
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove)
|
|
||||||
}
|
|
||||||
|
|
||||||
if vars.Scripts.PostRemove != "" {
|
|
||||||
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove)
|
|
||||||
}
|
|
||||||
|
|
||||||
if vars.Scripts.PreUpgrade != "" {
|
|
||||||
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
|
||||||
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
|
||||||
}
|
|
||||||
|
|
||||||
if vars.Scripts.PostUpgrade != "" {
|
|
||||||
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
|
||||||
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
|
||||||
}
|
|
||||||
|
|
||||||
if vars.Scripts.PreTrans != "" {
|
|
||||||
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans)
|
|
||||||
}
|
|
||||||
|
|
||||||
if vars.Scripts.PostTrans != "" {
|
|
||||||
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// archMatches checks if your system architecture matches
|
|
||||||
// one of the provided architectures
|
|
||||||
func archMatches(architectures []string) bool {
|
|
||||||
if slices.Contains(architectures, "all") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if slices.Contains(architectures, "arm") {
|
|
||||||
architectures = append(architectures, cpu.ARMVariant())
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices.Contains(architectures, cpu.Arch())
|
|
||||||
}
|
|
||||||
|
|
||||||
func setVersion(ctx context.Context, r *interp.Runner, to string) error {
|
|
||||||
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.Run(ctx, fl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// uniq removes all duplicates from string slices
|
|
||||||
func uniq(ss ...*[]string) {
|
|
||||||
for _, s := range ss {
|
|
||||||
slices.Sort(*s)
|
|
||||||
*s = slices.Compact(*s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
FROM alpine:latest
|
|
||||||
COPY lure-api-server /usr/bin/lure-api-server
|
|
||||||
ENTRYPOINT lure-api-server
|
|
@ -1,3 +0,0 @@
|
|||||||
# lure-api-server
|
|
||||||
|
|
||||||
`lure-api-server` is the backend API server for lure-web, the web interface for LURE.
|
|
@ -1,127 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/twitchtv/twirp"
|
|
||||||
"go.arsenm.dev/lure/internal/api"
|
|
||||||
"go.arsenm.dev/lure/internal/config"
|
|
||||||
"go.arsenm.dev/lure/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
type lureWebAPI struct {
|
|
||||||
db *sqlx.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l lureWebAPI) Search(ctx context.Context, req *api.SearchRequest) (*api.SearchResponse, error) {
|
|
||||||
query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
|
|
||||||
args := []any{"%" + req.Query + "%", "%" + req.Query + "%", req.Query}
|
|
||||||
|
|
||||||
if req.FilterValue != nil && req.FilterType != api.FILTER_TYPE_NO_FILTER {
|
|
||||||
switch req.FilterType {
|
|
||||||
case api.FILTER_TYPE_IN_REPOSITORY:
|
|
||||||
query += " AND repository = ?"
|
|
||||||
case api.FILTER_TYPE_SUPPORTS_ARCH:
|
|
||||||
query += " AND json_array_contains(architectures, ?)"
|
|
||||||
}
|
|
||||||
args = append(args, *req.FilterValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.SortBy != api.SORT_BY_UNSORTED {
|
|
||||||
switch req.SortBy {
|
|
||||||
case api.SORT_BY_NAME:
|
|
||||||
query += " ORDER BY name"
|
|
||||||
case api.SORT_BY_REPOSITORY:
|
|
||||||
query += " ORDER BY repository"
|
|
||||||
case api.SORT_BY_VERSION:
|
|
||||||
query += " ORDER BY version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Limit != 0 {
|
|
||||||
query += " LIMIT " + strconv.FormatInt(req.Limit, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.GetPkgs(l.db, query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out := &api.SearchResponse{}
|
|
||||||
for result.Next() {
|
|
||||||
pkg := &db.Package{}
|
|
||||||
err = result.StructScan(pkg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
out.Packages = append(out.Packages, dbPkgToAPI(pkg))
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l lureWebAPI) GetPkg(ctx context.Context, req *api.GetPackageRequest) (*api.Package, error) {
|
|
||||||
pkg, err := db.GetPkg(l.db, "name = ? AND repository = ?", req.Name, req.Repository)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dbPkgToAPI(pkg), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l lureWebAPI) GetBuildScript(ctx context.Context, req *api.GetBuildScriptRequest) (*api.GetBuildScriptResponse, error) {
|
|
||||||
if strings.ContainsAny(req.Name, "./") || strings.ContainsAny(req.Repository, "./") {
|
|
||||||
return nil, twirp.NewError(twirp.InvalidArgument, "name and repository must not contain . or /")
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptPath := filepath.Join(config.RepoDir, req.Repository, req.Name, "lure.sh")
|
|
||||||
_, err := os.Stat(scriptPath)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil, twirp.NewError(twirp.NotFound, "requested package not found")
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := os.ReadFile(scriptPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &api.GetBuildScriptResponse{Script: string(data)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbPkgToAPI(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,
|
|
||||||
Architectures: pkg.Architectures.Val,
|
|
||||||
Licenses: pkg.Licenses.Val,
|
|
||||||
Provides: pkg.Provides.Val,
|
|
||||||
Conflicts: pkg.Conflicts.Val,
|
|
||||||
Replaces: pkg.Replaces.Val,
|
|
||||||
Depends: dbMapToAPI(pkg.Depends.Val),
|
|
||||||
BuildDepends: dbMapToAPI(pkg.BuildDepends.Val),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptr[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbMapToAPI(m map[string][]string) map[string]*api.StringList {
|
|
||||||
out := make(map[string]*api.StringList, len(m))
|
|
||||||
for override, list := range m {
|
|
||||||
out[override] = &api.StringList{Entries: list}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go.arsenm.dev/logger/log"
|
|
||||||
"go.arsenm.dev/lure/internal/config"
|
|
||||||
"go.arsenm.dev/lure/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cfg types.Config
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
err := config.Decode(&cfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error decoding config file").Err(err).Send()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.arsenm.dev/logger/log"
|
|
||||||
"go.arsenm.dev/lure/internal/config"
|
|
||||||
"go.arsenm.dev/lure/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
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,14 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build
|
|
||||||
docker buildx build --platform linux/amd64 --tag arsen6331/lure-api-server:amd64 --no-cache .
|
|
||||||
|
|
||||||
CGO_ENABLED=0 GOARCH=arm64 GOOS=linux go build
|
|
||||||
docker buildx build --platform linux/arm64/v8 --tag arsen6331/lure-api-server:arm64 --no-cache .
|
|
||||||
|
|
||||||
docker login
|
|
||||||
docker push arsen6331/lure-api-server -a
|
|
||||||
|
|
||||||
docker manifest rm arsen6331/lure-api-server:latest
|
|
||||||
docker manifest create arsen6331/lure-api-server:latest --amend arsen6331/lure-api-server:arm64 --amend arsen6331/lure-api-server:amd64
|
|
||||||
docker manifest push arsen6331/lure-api-server:latest
|
|
@ -1,77 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/twitchtv/twirp"
|
|
||||||
"go.arsenm.dev/logger"
|
|
||||||
"go.arsenm.dev/logger/log"
|
|
||||||
"go.arsenm.dev/lure/internal/api"
|
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.Logger = logger.NewPretty(os.Stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
addr := flag.String("a", ":8080", "Listen address for API server")
|
|
||||||
logFile := flag.String("l", "", "Output file for JSON log")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *logFile != "" {
|
|
||||||
fl, err := os.Create(*logFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error creating log file").Err(err).Send()
|
|
||||||
}
|
|
||||||
defer fl.Close()
|
|
||||||
|
|
||||||
log.Logger = logger.NewMulti(log.Logger, logger.NewJSON(fl))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := repos.Pull(ctx, gdb, cfg.Repos)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
sigCh := make(chan struct{}, 200)
|
|
||||||
go repoPullWorker(ctx, sigCh)
|
|
||||||
|
|
||||||
var handler http.Handler
|
|
||||||
|
|
||||||
handler = api.NewAPIServer(
|
|
||||||
lureWebAPI{db: gdb},
|
|
||||||
twirp.WithServerPathPrefix(""),
|
|
||||||
)
|
|
||||||
handler = allowAllCORSHandler(handler)
|
|
||||||
handler = handleWebhook(handler, sigCh)
|
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", *addr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error starting listener").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Starting HTTP API server").Str("addr", ln.Addr().String()).Send()
|
|
||||||
|
|
||||||
err = http.Serve(ln, handler)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error while running server").Err(err).Send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func allowAllCORSHandler(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
||||||
res.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
res.Header().Set("Access-Control-Allow-Headers", "*")
|
|
||||||
if req.Method == http.MethodOptions {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.ServeHTTP(res, req)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.arsenm.dev/logger/log"
|
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleWebhook(next http.Handler, sigCh chan<- struct{}) http.Handler {
|
|
||||||
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
||||||
if req.URL.Path == "/webhook" {
|
|
||||||
if req.Method != http.MethodPost {
|
|
||||||
res.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Header.Get("X-GitHub-Event") != "push" {
|
|
||||||
http.Error(res, "Only push events are accepted by this bot", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := verifySecure(req)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(res, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sigCh <- struct{}{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next.ServeHTTP(res, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifySecure(req *http.Request) error {
|
|
||||||
sigStr := req.Header.Get("X-Hub-Signature-256")
|
|
||||||
sig, err := hex.DecodeString(strings.TrimPrefix(sigStr, "sha256="))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
secret := []byte(secretStr)
|
|
||||||
|
|
||||||
h := hmac.New(sha256.New, secret)
|
|
||||||
_, err = io.Copy(h, req.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hmac.Equal(h.Sum(nil), sig) {
|
|
||||||
log.Warn("Insecure webhook request").
|
|
||||||
Str("from", req.RemoteAddr).
|
|
||||||
Bytes("sig", sig).
|
|
||||||
Bytes("hmac", h.Sum(nil)).
|
|
||||||
Send()
|
|
||||||
|
|
||||||
return errors.New("webhook signature mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func repoPullWorker(ctx context.Context, sigCh <-chan struct{}) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-sigCh:
|
|
||||||
err := repos.Pull(ctx, gdb, cfg.Repos)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error while pulling repositories").Err(err).Send()
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
36
config.go
36
config.go
@ -1,36 +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 main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go.arsenm.dev/logger/log"
|
|
||||||
"go.arsenm.dev/lure/internal/config"
|
|
||||||
"go.arsenm.dev/lure/internal/types"
|
|
||||||
"go.arsenm.dev/lure/manager"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cfg types.Config
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
err := config.Decode(&cfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error decoding config file").Err(err).Send()
|
|
||||||
}
|
|
||||||
manager.DefaultRootCmd = cfg.RootCmd
|
|
||||||
}
|
|
32
db.go
32
db.go
@ -1,32 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.arsenm.dev/logger/log"
|
|
||||||
"go.arsenm.dev/lure/internal/config"
|
|
||||||
"go.arsenm.dev/lure/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -36,7 +36,7 @@ The `repo` array in the config specifies which repos are added to LURE. Each rep
|
|||||||
```toml
|
```toml
|
||||||
[[repo]]
|
[[repo]]
|
||||||
name = 'default'
|
name = 'default'
|
||||||
url = 'https://github.com/Arsen6331/lure-repo.git'
|
url = 'https://github.com/Elara6331/lure-repo.git'
|
||||||
```
|
```
|
||||||
|
|
||||||
The `default` repo is added by default. Any amount of repos may be added.
|
The `default` repo is added by default. Any amount of repos may be added.
|
||||||
|
@ -5,19 +5,23 @@
|
|||||||
- `go` (1.18+)
|
- `go` (1.18+)
|
||||||
- `git`
|
- `git`
|
||||||
- `lure-analyzer`
|
- `lure-analyzer`
|
||||||
- `go install go.arsenm.dev/lure-repo-bot/cmd/lure-analyzer@latest`
|
- `go install go.elara.ws/lure-repo-bot/cmd/lure-analyzer@latest`
|
||||||
- `shfmt`
|
- `shfmt`
|
||||||
- May be available in distro repos
|
- May be available in distro repos
|
||||||
- `go install mvdan.cc/sh/v3/cmd/shfmt@latest`
|
- `go install mvdan.cc/sh/v3/cmd/shfmt@latest`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## How to test a package
|
||||||
|
|
||||||
|
To test packages you can first create [a `lure.sh` shell file](./build-scripts.md) and then run the `lure build` comand to build the local `lure.sh` file into a package for your distro (more info about the `build` command [here](./usage.md#build)). You can then install this file to your distro and test it.
|
||||||
|
|
||||||
## How to submit a package
|
## How to submit a package
|
||||||
|
|
||||||
LURE's repo is hosted on Github at https://github.com/Arsen6331/lure-repo. In it, there are multiple directories each containing a `lure.sh` file. In order to add a package to LURE's repo, simply create a PR with a [build script](./build-scripts.md) and place it in a directory with the same name as the package.
|
LURE's repo is hosted on Github at https://github.com/Elara6331/lure-repo. In it, there are multiple directories each containing a `lure.sh` file. In order to add a package to LURE's repo, simply create a PR with a [build script](./build-scripts.md) and place it in a directory with the same name as the package.
|
||||||
|
|
||||||
Upon submitting the PR, [lure-repo-bot](https://github.com/Arsen6331/lure-repo-bot) will pull your PR and analyze it, providing suggestions for fixes as review comments. If there are no problems, the bot will approve your changes. If there are issues, re-request review from the bot after you've finished applying the fixes and it will automatically review the PR again.
|
Upon submitting the PR, [lure-repo-bot](https://github.com/Elara6331/lure-repo-bot) will pull your PR and analyze it, providing suggestions for fixes as review comments. If there are no problems, the bot will approve your changes. If there are issues, re-request review from the bot after you've finished applying the fixes and it will automatically review the PR again.
|
||||||
|
|
||||||
All scripts submitted to the LURE repo should be formatted with `shfmt`. If they are not properly formatted, Github Actions will add suggestions in the "Files Changed" tab of the PR.
|
All scripts submitted to the LURE repo should be formatted with `shfmt`. If they are not properly formatted, Github Actions will add suggestions in the "Files Changed" tab of the PR.
|
||||||
|
|
||||||
Once your PR is merged, LURE will pull the changed repo and your package will be available for people to install.
|
Once your PR is merged, LURE will pull the changed repo and your package will be available for people to install.
|
||||||
|
@ -21,6 +21,7 @@ LURE uses build scripts similar to the AUR's PKGBUILDs. This is the documentatio
|
|||||||
- [conflicts](#conflicts)
|
- [conflicts](#conflicts)
|
||||||
- [deps](#deps)
|
- [deps](#deps)
|
||||||
- [build_deps](#build_deps)
|
- [build_deps](#build_deps)
|
||||||
|
- [opt_deps](#opt_deps)
|
||||||
- [replaces](#replaces)
|
- [replaces](#replaces)
|
||||||
- [sources](#sources)
|
- [sources](#sources)
|
||||||
- [checksums](#checksums)
|
- [checksums](#checksums)
|
||||||
@ -54,7 +55,7 @@ LURE uses build scripts similar to the AUR's PKGBUILDs. This is the documentatio
|
|||||||
|
|
||||||
## Distro Overrides
|
## Distro Overrides
|
||||||
|
|
||||||
Allowing LURE to run on different distros provides some challenges. For example, some distros use different names for their packages. This is solved using distro overrides. Any variable or function used in a LURE build script may be overridden based on distro and CPU architecture. The way you do this is by appending the distro and/or architecture to the end of the name. For example, [ITD](https://gitea.arsenm.dev/Arsen6331/itd) depends on the `pactl` command as well as DBus and BlueZ. These are named somewhat differently on different distros. For ITD, I use the following for the dependencies:
|
Allowing LURE to run on different distros provides some challenges. For example, some distros use different names for their packages. This is solved using distro overrides. Any variable or function used in a LURE build script may be overridden based on distro and CPU architecture. The way you do this is by appending the distro and/or architecture to the end of the name. For example, [ITD](https://gitea.elara.ws/Elara6331/itd) depends on the `pactl` command as well as DBus and BlueZ. These are named somewhat differently on different distros. For ITD, I use the following for the dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
deps=('dbus' 'bluez' 'pulseaudio-utils')
|
deps=('dbus' 'bluez' 'pulseaudio-utils')
|
||||||
@ -117,7 +118,7 @@ The `homepage` field contains the URL to the website of the project packaged by
|
|||||||
The `maintainer` field contains the name and email address of the person maintaining the package. Example:
|
The `maintainer` field contains the name and email address of the person maintaining the package. Example:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Arsen Musayelyan <arsen@arsenm.dev>
|
Elara Musayelyan <elara@elara.ws>
|
||||||
```
|
```
|
||||||
|
|
||||||
While LURE does not require this field to be set, Debian has deprecated unset maintainer fields, and may disallow their use in `.deb` packages in the future.
|
While LURE does not require this field to be set, Debian has deprecated unset maintainer fields, and may disallow their use in `.deb` packages in the future.
|
||||||
@ -160,6 +161,17 @@ The `deps` array contains the dependencies for the package. LURE repos will be c
|
|||||||
|
|
||||||
The `build_deps` array contains the dependencies that are required to build the package. They will be installed before the build starts. Similarly to the `deps` array, LURE repos will be checked first.
|
The `build_deps` array contains the dependencies that are required to build the package. They will be installed before the build starts. Similarly to the `deps` array, LURE repos will be checked first.
|
||||||
|
|
||||||
|
### opt_deps
|
||||||
|
|
||||||
|
The `opt_deps` array contains optional dependencies for the package. A description can be added after ": ", but it's not required.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opt_deps_arch=(
|
||||||
|
'git: Download git repositories'
|
||||||
|
'aria2: Download files'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
### replaces
|
### replaces
|
||||||
|
|
||||||
The `replaces` array contains the packages that are replaced by this package. Generally, if package managers find a package with a `replaces` field set, they will remove the listed package(s) and install that one instead. This is only useful if the packages are being stored in a repo for your package manager.
|
The `replaces` array contains the packages that are replaced by this package. Generally, if package managers find a package with a `replaces` field set, they will remove the listed package(s) and install that one instead. This is only useful if the packages are being stored in a repo for your package manager.
|
||||||
@ -182,25 +194,26 @@ 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:
|
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.
|
- `~rev`: Specify which revision 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.
|
|
||||||
- `~depth`: Specify what depth should be used when cloning the repo. Must be an integer.
|
- `~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.
|
- `~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:
|
Examples:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
git+https://gitea.arsenm.dev/Arsen6331/itd?~branch=resource-loading&~depth=1
|
git+https://gitea.elara.ws/Elara6331/itd?~rev=resource-loading&~depth=1
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text
|
||||||
git+https://gitea.arsenm.dev/Arsen6331/lure?~tag=v0.0.1
|
git+https://gitea.elara.ws/lure/lure?~rev=v0.0.1&~recursive=true
|
||||||
```
|
```
|
||||||
|
|
||||||
### checksums
|
### checksums
|
||||||
|
|
||||||
The `checksums` array must be the same length as the `sources` array. It contains sha256 checksums for the source files. The files are checked against the checksums and the build fails if they don't match.
|
The `checksums` array must be the same length as the `sources` array. It contains checksums for the source files. The files are checked against the checksums and the build fails if they don't match.
|
||||||
|
|
||||||
|
By default, checksums are expected to be sha256. To change the algorithm, add it before the hash with a colon in between. For example, `md5:bc0c6f5dcd06bddbca9a0163e4c9f2e1`. The following algorithms are currently supported: `sha256`, `sha224`, `sha512`, `sha384`, `sha1`, and `md5`.
|
||||||
|
|
||||||
To skip the check for a particular source, set the corresponding checksum to `SKIP`.
|
To skip the check for a particular source, set the corresponding checksum to `SKIP`.
|
||||||
|
|
||||||
|
@ -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.
|
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:
|
Examples:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
lure in itd-bin # only finds itd-bin
|
lure in itd-bin # only finds itd-bin
|
||||||
lure in itd # finds itd-bin and itd-git
|
lure in itd # finds itd-bin and itd-git
|
||||||
lure in it% # finds itd-bin, itd-git, and itgui-git
|
lure in it% # finds itd-bin, itd-git, and itgui-git
|
||||||
|
lure in -c itd-bin
|
||||||
```
|
```
|
||||||
|
|
||||||
### remove
|
### 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.
|
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:
|
Example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -112,7 +117,7 @@ The addrepo command adds a repository to LURE if it doesn't already exist. The `
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
lure ar -n default -u https://github.com/Arsen6331/lure-repo
|
lure ar -n default -u https://github.com/Elara6331/lure-repo
|
||||||
```
|
```
|
||||||
|
|
||||||
### removerepo
|
### removerepo
|
||||||
|
@ -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
|
|
||||||
}
|
|
82
fix.go
82
fix.go
@ -1,52 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.arsenm.dev/logger/log"
|
"lure.sh/lure/internal/config"
|
||||||
"go.arsenm.dev/lure/internal/config"
|
"lure.sh/lure/internal/db"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
"lure.sh/lure/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
func fixCmd(c *cli.Context) error {
|
var fixCmd = &cli.Command{
|
||||||
gdb.Close()
|
Name: "fix",
|
||||||
|
Usage: "Attempt to fix problems with LURE",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
log.Info("Removing cache directory").Send()
|
db.Close()
|
||||||
|
paths := config.GetPaths(ctx)
|
||||||
|
|
||||||
err := os.RemoveAll(config.CacheDir)
|
log.Info("Removing cache directory").Send()
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to remove cache directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Rebuilding cache").Send()
|
err := os.RemoveAll(paths.CacheDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to remove cache directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(config.CacheDir, 0o755)
|
log.Info("Rebuilding cache").Send()
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to create new cache directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
gdb, err = sqlx.Open("sqlite", config.DBPath)
|
err = os.MkdirAll(paths.CacheDir, 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to create new database").Err(err).Send()
|
log.Fatal("Unable to create new cache directory").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the DB is rebuilt when repos are pulled
|
err = repos.Pull(ctx, config.Config(ctx).Repos)
|
||||||
config.DBPresent = false
|
if err != nil {
|
||||||
|
log.Fatal("Error pulling repos").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
err = db.Init(gdb)
|
log.Info("Done").Send()
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error initializing database").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = repos.Pull(c.Context, gdb, cfg.Repos)
|
return nil
|
||||||
if err != nil {
|
},
|
||||||
log.Fatal("Error pulling repos").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Done").Send()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
45
gen.go
Normal file
45
gen.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"lure.sh/lure/pkg/gen"
|
||||||
|
)
|
||||||
|
|
||||||
|
var genCmd = &cli.Command{
|
||||||
|
Name: "generate",
|
||||||
|
Usage: "Generate a LURE script from a template",
|
||||||
|
Aliases: []string{"gen"},
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
genPipCmd,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var genPipCmd = &cli.Command{
|
||||||
|
Name: "pip",
|
||||||
|
Usage: "Generate a LURE script for a pip module",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "version",
|
||||||
|
Aliases: []string{"v"},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return gen.Pip(os.Stdout, gen.PipOptions{
|
||||||
|
Name: c.String("name"),
|
||||||
|
Version: c.String("version"),
|
||||||
|
Description: c.String("description"),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
140
go.mod
140
go.mod
@ -1,105 +1,125 @@
|
|||||||
module go.arsenm.dev/lure
|
module lure.sh/lure
|
||||||
|
|
||||||
go 1.18
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.21.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/alecthomas/chroma/v2 v2.4.0
|
github.com/PuerkitoBio/purell v1.2.0
|
||||||
github.com/charmbracelet/bubbles v0.14.0
|
github.com/alecthomas/chroma/v2 v2.9.1
|
||||||
github.com/charmbracelet/bubbletea v0.23.1
|
github.com/charmbracelet/bubbles v0.16.1
|
||||||
github.com/charmbracelet/lipgloss v0.6.0
|
github.com/charmbracelet/bubbletea v0.24.2
|
||||||
github.com/go-git/go-billy/v5 v5.3.1
|
github.com/charmbracelet/lipgloss v0.8.0
|
||||||
github.com/go-git/go-git/v5 v5.4.2
|
github.com/go-git/go-billy/v5 v5.5.0
|
||||||
github.com/goreleaser/nfpm/v2 v2.20.0
|
github.com/go-git/go-git/v5 v5.9.0
|
||||||
|
github.com/goreleaser/nfpm/v2 v2.33.0
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/jmoiron/sqlx v1.3.5
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
github.com/mattn/go-isatty v0.0.19
|
||||||
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/muesli/reflow v0.3.0
|
github.com/muesli/reflow v0.3.0
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5
|
github.com/pelletier/go-toml/v2 v2.1.0
|
||||||
github.com/twitchtv/twirp v8.1.3+incompatible
|
github.com/schollz/progressbar/v3 v3.13.1
|
||||||
github.com/urfave/cli/v2 v2.16.3
|
github.com/urfave/cli/v2 v2.25.7
|
||||||
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668
|
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||||
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
|
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090
|
||||||
golang.org/x/sys v0.3.0
|
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6
|
||||||
google.golang.org/protobuf v1.27.1
|
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
|
||||||
|
golang.org/x/crypto v0.13.0
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||||
|
golang.org/x/sys v0.12.0
|
||||||
|
golang.org/x/text v0.13.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
modernc.org/sqlite v1.20.0
|
lure.sh/fakeroot v0.0.0-20231024000108-b130d64a68ee
|
||||||
mvdan.cc/sh/v3 v3.5.1
|
modernc.org/sqlite v1.25.0
|
||||||
|
mvdan.cc/sh/v3 v3.7.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c // indirect
|
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||||
|
github.com/bodgit/plumbing v1.2.0 // indirect
|
||||||
|
github.com/bodgit/sevenzip v1.3.0 // indirect
|
||||||
|
github.com/bodgit/windows v1.0.0 // indirect
|
||||||
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
||||||
github.com/containerd/console v1.0.3 // indirect
|
github.com/cloudflare/circl v1.3.3 // indirect
|
||||||
|
github.com/connesc/cipherio v0.2.1 // indirect
|
||||||
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||||
|
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||||
github.com/dsnet/compress v0.0.1 // indirect
|
github.com/dsnet/compress v0.0.1 // indirect
|
||||||
github.com/emirpasic/gods v1.12.0 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/frankban/quicktest v1.14.3 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/go-git/gcfg v1.5.0 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/gobwas/glob v0.2.3 // indirect
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/rpmpack v0.5.0 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/gookit/color v1.5.2 // indirect
|
github.com/gookit/color v1.5.1 // indirect
|
||||||
github.com/goreleaser/chglog v0.2.2 // indirect
|
github.com/goreleaser/chglog v0.5.0 // indirect
|
||||||
github.com/goreleaser/fileglob v1.3.0 // indirect
|
github.com/goreleaser/fileglob v1.3.0 // indirect
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.13 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/huandu/xstrings v1.3.3 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/klauspost/compress v1.15.11 // indirect
|
github.com/klauspost/compress v1.17.0 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.11 // indirect
|
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.13.0 // indirect
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sergi/go-diff v1.2.0 // indirect
|
github.com/sergi/go-diff v1.2.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
github.com/shopspring/decimal v1.2.0 // indirect
|
||||||
|
github.com/skeema/knownhosts v1.2.0 // indirect
|
||||||
|
github.com/spf13/cast v1.5.1 // indirect
|
||||||
github.com/therootcompany/xz v1.0.1 // indirect
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/mod v0.12.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
|
golang.org/x/net v0.15.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
golang.org/x/term v0.12.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/tools v0.13.0 // indirect
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
lukechampine.com/uint128 v1.2.0 // indirect
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
modernc.org/cc/v3 v3.40.0 // indirect
|
||||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||||
modernc.org/libc v1.21.5 // indirect
|
modernc.org/libc v1.24.1 // indirect
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
modernc.org/memory v1.4.0 // indirect
|
modernc.org/memory v1.6.0 // indirect
|
||||||
modernc.org/opt v0.1.3 // indirect
|
modernc.org/opt v0.1.3 // indirect
|
||||||
modernc.org/strutil v1.1.3 // indirect
|
modernc.org/strutil v1.1.3 // indirect
|
||||||
modernc.org/token v1.0.1 // indirect
|
modernc.org/token v1.0.1 // indirect
|
||||||
|
627
go.sum
627
go.sum
@ -1,149 +1,232 @@
|
|||||||
github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
|
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
|
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||||
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||||
|
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
|
||||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
|
||||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
|
||||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
|
||||||
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c h1:bNpaLLv2Y4kslsdkdCwAYu8Bak1aGVtxwi8Z/wy4Yuo=
|
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f h1:CGq7OieOz3wyQJ1fO8S0eO9TCW1JyvLrf8fhzz1i8ko=
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
|
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
|
||||||
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs=
|
||||||
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
github.com/PuerkitoBio/purell v1.2.0 h1:/Jdm5QfyM8zdlqT6WVZU4cfP23sot6CEHA4CS49Ezig=
|
||||||
github.com/alecthomas/assert/v2 v2.2.0 h1:f6L/b7KE2bfA+9O4FL3CM/xJccDEwPVYd5fALBiuwvw=
|
github.com/PuerkitoBio/purell v1.2.0/go.mod h1:OhLRTaaIzhvIyofkJfB24gokC7tM42Px5UhoT32THBk=
|
||||||
github.com/alecthomas/chroma/v2 v2.4.0 h1:Loe2ZjT5x3q1bcWwemqyqEi8p11/IV/ncFCeLYDpWC4=
|
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||||
github.com/alecthomas/chroma/v2 v2.4.0/go.mod h1:6kHzqF5O6FUSJzBXW7fXELjb+e+7OXW4UpoPqMO7IBQ=
|
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||||
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||||
|
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||||
|
github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU=
|
||||||
|
github.com/alecthomas/chroma/v2 v2.9.1/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||||
|
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||||
|
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||||
|
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
|
||||||
|
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
|
||||||
|
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
|
||||||
|
github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM=
|
||||||
|
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
|
||||||
|
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||||
|
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg=
|
github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg=
|
||||||
|
github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11/go.mod h1:je2KZ+LxaCNvCoKg32jtOIULcFogJKcL1ZWUaIBjKj0=
|
||||||
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
|
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
|
||||||
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
|
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
|
||||||
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
|
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
|
||||||
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
|
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
|
||||||
github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
|
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||||
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
|
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||||
github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck=
|
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
||||||
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
|
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
|
||||||
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
|
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
|
||||||
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||||
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
|
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
||||||
|
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
||||||
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||||
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||||
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
|
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||||
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
|
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||||
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
|
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||||
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||||
|
github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY=
|
||||||
|
github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A=
|
||||||
|
github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
|
||||||
github.com/goreleaser/chglog v0.2.2 h1:V7nf07baXtGAgGevvqgW2MM4kZ6gOr12vKNSAU3VIZ0=
|
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
|
||||||
github.com/goreleaser/chglog v0.2.2/go.mod h1:2s5JwtCOWjZa8AIneL+xdUl9SRuigCjRHNHsX30dupE=
|
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||||
|
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||||
|
github.com/goreleaser/chglog v0.5.0 h1:Sk6BMIpx8+vpAf8KyPit34OgWui8c7nKTMHhYx88jJ4=
|
||||||
|
github.com/goreleaser/chglog v0.5.0/go.mod h1:Ri46M3lrMuv76FHszs3vtABR8J8k1w9JHYAzxeeOl28=
|
||||||
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
|
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
|
||||||
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
|
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
|
||||||
github.com/goreleaser/nfpm/v2 v2.20.0 h1:Q/CrX54KUMluz6+M/pjTbknFd5Dao8qXi0C6ZuFCtfY=
|
github.com/goreleaser/nfpm/v2 v2.33.0 h1:yBv6jgkPwih4va/S42rceSjJ2Znt3Og/Ntc76oP0tfI=
|
||||||
github.com/goreleaser/nfpm/v2 v2.20.0/go.mod h1:/Fh6XfwT/T+D4qtNC2iXmHSD/1UT20JkvBXyJ6nFmOY=
|
github.com/goreleaser/nfpm/v2 v2.33.0/go.mod h1:8wwWWvJWmn84xo/Sqiv0aMvEGTHlHZTXTEuVSgQpkIM=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||||
|
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
|
||||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
@ -151,165 +234,332 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
|||||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
|
||||||
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.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.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7 h1:xzByj8G8tj0Oq7ZYYU4+ixL/CVb5ruWCm0EZQ1PjOkE=
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7/go.mod h1:Fs8qUkO74HHaidabihzYephJH8qmGD/nCP6tE5xC9BM=
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||||
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||||
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
|
|
||||||
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
|
|
||||||
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 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
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/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||||
|
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
|
||||||
|
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
|
||||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
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=
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||||
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM=
|
||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
|
||||||
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU=
|
||||||
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY=
|
||||||
|
github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w=
|
||||||
|
github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg=
|
||||||
|
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||||
|
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||||
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
|
|
||||||
github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
|
||||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
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.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
|
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||||
github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||||
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||||
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
|
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.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
|
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=
|
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.elara.ws/logger v0.0.0-20230421022458-e80700db2090 h1:RVC8XvWo6Yw4HUshqx4TSzuBDScDghafU6QFRJ4xPZg=
|
||||||
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
|
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
|
||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6 h1:4xCBxLPBn3Y2DuIcj8zQ1tQOFLrpu6tEIGUWn/Q6zPM=
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6/go.mod h1:NmfCFqwq7X/aqa/ZVkIysj17JyMEY4Bb5E921kMswNo=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU=
|
||||||
|
go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
|
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
|
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
|
golang.org/x/sys v0.12.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
|
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||||
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
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.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||||
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
@ -319,32 +569,45 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
|
lure.sh/fakeroot v0.0.0-20231024000108-b130d64a68ee h1:kSXIuMid56Q29WEl7EQb5QUtmGqQqAu66EZ2G0OSUfU=
|
||||||
|
lure.sh/fakeroot v0.0.0-20231024000108-b130d64a68ee/go.mod h1:/v0u0AZ+wbzUWhV02KzciOf1KFNh7/7rbkz5Z0b5gDA=
|
||||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||||
|
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||||
modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI=
|
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||||
modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
|
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
||||||
|
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
||||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
|
modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
|
||||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
modernc.org/sqlite v1.20.0 h1:80zmD3BGkm8BZ5fUi/4lwJQHiO3GXgIUvZRXpoIfROY=
|
modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA=
|
||||||
modernc.org/sqlite v1.20.0/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw=
|
modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
|
||||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||||
|
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
||||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||||
mvdan.cc/sh/v3 v3.5.1 h1:hmP3UOw4f+EYexsJjFxvU38+kn+V/s2CclXHanIBkmQ=
|
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
||||||
mvdan.cc/sh/v3 v3.5.1/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
|
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
|
||||||
|
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
86
helper.go
Normal file
86
helper.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"lure.sh/lure/internal/cpu"
|
||||||
|
"lure.sh/lure/internal/shutils/helpers"
|
||||||
|
"lure.sh/lure/pkg/distro"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var helperCmd = &cli.Command{
|
||||||
|
Name: "helper",
|
||||||
|
Usage: "Run a LURE helper command",
|
||||||
|
ArgsUsage: `<helper_name|"list">`,
|
||||||
|
Subcommands: []*cli.Command{helperListCmd},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "dest-dir",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "The directory that the install commands will install to",
|
||||||
|
Value: "dest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
|
if c.Args().Len() < 1 {
|
||||||
|
cli.ShowSubcommandHelpAndExit(c, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
helper, ok := helpers.Helpers[c.Args().First()]
|
||||||
|
if !ok {
|
||||||
|
log.Fatal("No such helper command").Str("name", c.Args().First()).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error getting working directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := distro.ParseOSRelease(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error getting working directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
hc := interp.HandlerContext{
|
||||||
|
Env: expand.ListEnviron(
|
||||||
|
"pkgdir="+c.String("dest-dir"),
|
||||||
|
"DISTRO_ID="+info.ID,
|
||||||
|
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
||||||
|
"ARCH="+cpu.Arch(),
|
||||||
|
),
|
||||||
|
Dir: wd,
|
||||||
|
Stdin: os.Stdin,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
return helper(hc, c.Args().First(), c.Args().Slice()[1:])
|
||||||
|
},
|
||||||
|
CustomHelpTemplate: cli.CommandHelpTemplate,
|
||||||
|
BashComplete: func(ctx *cli.Context) {
|
||||||
|
for name := range helpers.Helpers {
|
||||||
|
fmt.Println(name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var helperListCmd = &cli.Command{
|
||||||
|
Name: "list",
|
||||||
|
Usage: "List all the available helper commands",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
for name := range helpers.Helpers {
|
||||||
|
fmt.Println(name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
133
info.go
133
info.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -22,80 +22,85 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"go.arsenm.dev/logger/log"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.arsenm.dev/lure/distro"
|
"lure.sh/lure/internal/cliutils"
|
||||||
"go.arsenm.dev/lure/internal/cliutils"
|
"lure.sh/lure/internal/config"
|
||||||
"go.arsenm.dev/lure/internal/overrides"
|
"lure.sh/lure/internal/overrides"
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
"lure.sh/lure/pkg/distro"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
"lure.sh/lure/pkg/repos"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func infoCmd(c *cli.Context) error {
|
var infoCmd = &cli.Command{
|
||||||
args := c.Args()
|
Name: "info",
|
||||||
if args.Len() < 1 {
|
Usage: "Print information about a package",
|
||||||
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
|
Flags: []cli.Flag{
|
||||||
}
|
&cli.BoolFlag{
|
||||||
|
Name: "all",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "Show all information, not just for the current distro",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
args := c.Args()
|
||||||
if err != nil {
|
if args.Len() < 1 {
|
||||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
|
||||||
}
|
|
||||||
|
|
||||||
found, _, err := repos.FindPkgs(gdb, args.Slice())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error finding packages").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(found) == 0 {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgs := cliutils.FlattenPkgs(found, "show")
|
|
||||||
|
|
||||||
var names []string
|
|
||||||
all := c.Bool("all")
|
|
||||||
|
|
||||||
if !all {
|
|
||||||
info, err := distro.ParseOSRelease(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error parsing os-release file").Err(err).Send()
|
|
||||||
}
|
}
|
||||||
names = overrides.Resolve(info, overrides.DefaultOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pkg := range pkgs {
|
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
found, _, err := repos.FindPkgs(ctx, args.Slice())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error finding packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(found) == 0 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
all := c.Bool("all")
|
||||||
|
|
||||||
if !all {
|
if !all {
|
||||||
depsSet := false
|
info, err := distro.ParseOSRelease(ctx)
|
||||||
buildDepsSet := false
|
if err != nil {
|
||||||
for _, name := range names {
|
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
names, err = overrides.Resolve(
|
||||||
if !depsSet {
|
info,
|
||||||
pkg.Depends.Val = nil
|
overrides.DefaultOpts.
|
||||||
}
|
WithLanguages([]string{config.SystemLang()}),
|
||||||
|
)
|
||||||
if !buildDepsSet {
|
if err != nil {
|
||||||
pkg.BuildDepends.Val = nil
|
log.Fatal("Error resolving overrides").Err(err).Send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
for _, pkg := range pkgs {
|
||||||
if err != nil {
|
if !all {
|
||||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
|
||||||
}
|
if err != nil {
|
||||||
fmt.Println("---")
|
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
fmt.Println("---")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
170
install.go
170
install.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -19,98 +19,104 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"go.arsenm.dev/logger/log"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.arsenm.dev/lure/internal/cliutils"
|
"lure.sh/lure/internal/cliutils"
|
||||||
"go.arsenm.dev/lure/internal/config"
|
"lure.sh/lure/internal/config"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/internal/db"
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
"lure.sh/lure/internal/types"
|
||||||
"go.arsenm.dev/lure/manager"
|
"lure.sh/lure/pkg/build"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
"lure.sh/lure/pkg/manager"
|
||||||
|
"lure.sh/lure/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
func installCmd(c *cli.Context) error {
|
var installCmd = &cli.Command{
|
||||||
args := c.Args()
|
Name: "install",
|
||||||
if args.Len() < 1 {
|
Usage: "Install a new package",
|
||||||
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
|
Aliases: []string{"in"},
|
||||||
}
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "clean",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Build package from scratch even if there's an already built package available",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
mgr := manager.Detect()
|
args := c.Args()
|
||||||
if mgr == nil {
|
if args.Len() < 1 {
|
||||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
|
||||||
}
|
|
||||||
|
|
||||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
found, notFound, err := repos.FindPkgs(gdb, args.Slice())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error finding packages").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
installPkgs(c.Context, cliutils.FlattenPkgs(found, "install"), notFound, mgr)
|
|
||||||
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) {
|
|
||||||
if len(notFound) > 0 {
|
|
||||||
err := mgr.Install(nil, notFound...)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error installing native packages").Err(err).Send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
installScripts(ctx, mgr, getScriptPaths(pkgs))
|
|
||||||
}
|
|
||||||
|
|
||||||
// getScriptPaths generates a slice of script paths corresponding to the
|
|
||||||
// given packages
|
|
||||||
func getScriptPaths(pkgs []db.Package) []string {
|
|
||||||
var scripts []string
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
scriptPath := filepath.Join(config.RepoDir, pkg.Repository, pkg.Name, "lure.sh")
|
|
||||||
scripts = append(scripts, scriptPath)
|
|
||||||
}
|
|
||||||
return scripts
|
|
||||||
}
|
|
||||||
|
|
||||||
// installScripts builds and installs LURE build scripts
|
|
||||||
func installScripts(ctx context.Context, mgr manager.Manager, scripts []string) {
|
|
||||||
for _, script := range scripts {
|
|
||||||
builtPkgs, _, err := buildPackage(ctx, script, mgr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error building package").Err(err).Send()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mgr.InstallLocal(nil, builtPkgs...)
|
mgr := manager.Detect()
|
||||||
if err != nil {
|
if mgr == nil {
|
||||||
log.Fatal("Error installing package").Err(err).Send()
|
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
found, notFound, err := repos.FindPkgs(ctx, args.Slice())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error finding packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
|
||||||
|
build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
|
||||||
|
Manager: mgr,
|
||||||
|
Clean: c.Bool("clean"),
|
||||||
|
Interactive: c.Bool("interactive"),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
BashComplete: func(c *cli.Context) {
|
||||||
|
log := loggerctx.From(c.Context)
|
||||||
|
result, err := db.GetPkgs(c.Context, "true")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error getting packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
defer result.Close()
|
||||||
|
|
||||||
|
for result.Next() {
|
||||||
|
var pkg db.Package
|
||||||
|
err = result.StructScan(&pkg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error iterating over packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(pkg.Name)
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeCmd(c *cli.Context) error {
|
var removeCmd = &cli.Command{
|
||||||
args := c.Args()
|
Name: "remove",
|
||||||
if args.Len() < 1 {
|
Usage: "Remove an installed package",
|
||||||
log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send()
|
Aliases: []string{"rm"},
|
||||||
}
|
Action: func(c *cli.Context) error {
|
||||||
|
log := loggerctx.From(c.Context)
|
||||||
|
|
||||||
mgr := manager.Detect()
|
args := c.Args()
|
||||||
if mgr == nil {
|
if args.Len() < 1 {
|
||||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mgr.Remove(nil, c.Args().Slice()...)
|
mgr := manager.Detect()
|
||||||
if err != nil {
|
if mgr == nil {
|
||||||
log.Fatal("Error removing packages").Err(err).Send()
|
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
err := mgr.Remove(nil, c.Args().Slice()...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error removing packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
//go:generate protoc --twirp_out=. lure.proto
|
|
||||||
//go:generate protoc --go_out=. lure.proto
|
|
@ -1,885 +0,0 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.28.1
|
|
||||||
// protoc v3.21.9
|
|
||||||
// source: lure.proto
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
// SORT_BY represents possible things to sort packages by
|
|
||||||
type SORT_BY int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
SORT_BY_UNSORTED SORT_BY = 0
|
|
||||||
SORT_BY_NAME SORT_BY = 1
|
|
||||||
SORT_BY_REPOSITORY SORT_BY = 2
|
|
||||||
SORT_BY_VERSION SORT_BY = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// Enum value maps for SORT_BY.
|
|
||||||
var (
|
|
||||||
SORT_BY_name = map[int32]string{
|
|
||||||
0: "UNSORTED",
|
|
||||||
1: "NAME",
|
|
||||||
2: "REPOSITORY",
|
|
||||||
3: "VERSION",
|
|
||||||
}
|
|
||||||
SORT_BY_value = map[string]int32{
|
|
||||||
"UNSORTED": 0,
|
|
||||||
"NAME": 1,
|
|
||||||
"REPOSITORY": 2,
|
|
||||||
"VERSION": 3,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (x SORT_BY) Enum() *SORT_BY {
|
|
||||||
p := new(SORT_BY)
|
|
||||||
*p = x
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x SORT_BY) String() string {
|
|
||||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (SORT_BY) Descriptor() protoreflect.EnumDescriptor {
|
|
||||||
return file_lure_proto_enumTypes[0].Descriptor()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (SORT_BY) Type() protoreflect.EnumType {
|
|
||||||
return &file_lure_proto_enumTypes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x SORT_BY) Number() protoreflect.EnumNumber {
|
|
||||||
return protoreflect.EnumNumber(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use SORT_BY.Descriptor instead.
|
|
||||||
func (SORT_BY) EnumDescriptor() ([]byte, []int) {
|
|
||||||
return file_lure_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FILTER_TYPE represents possible filters for packages
|
|
||||||
type FILTER_TYPE int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
FILTER_TYPE_NO_FILTER FILTER_TYPE = 0
|
|
||||||
FILTER_TYPE_IN_REPOSITORY FILTER_TYPE = 1
|
|
||||||
FILTER_TYPE_SUPPORTS_ARCH FILTER_TYPE = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// Enum value maps for FILTER_TYPE.
|
|
||||||
var (
|
|
||||||
FILTER_TYPE_name = map[int32]string{
|
|
||||||
0: "NO_FILTER",
|
|
||||||
1: "IN_REPOSITORY",
|
|
||||||
2: "SUPPORTS_ARCH",
|
|
||||||
}
|
|
||||||
FILTER_TYPE_value = map[string]int32{
|
|
||||||
"NO_FILTER": 0,
|
|
||||||
"IN_REPOSITORY": 1,
|
|
||||||
"SUPPORTS_ARCH": 2,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (x FILTER_TYPE) Enum() *FILTER_TYPE {
|
|
||||||
p := new(FILTER_TYPE)
|
|
||||||
*p = x
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x FILTER_TYPE) String() string {
|
|
||||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (FILTER_TYPE) Descriptor() protoreflect.EnumDescriptor {
|
|
||||||
return file_lure_proto_enumTypes[1].Descriptor()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (FILTER_TYPE) Type() protoreflect.EnumType {
|
|
||||||
return &file_lure_proto_enumTypes[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x FILTER_TYPE) Number() protoreflect.EnumNumber {
|
|
||||||
return protoreflect.EnumNumber(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use FILTER_TYPE.Descriptor instead.
|
|
||||||
func (FILTER_TYPE) EnumDescriptor() ([]byte, []int) {
|
|
||||||
return file_lure_proto_rawDescGZIP(), []int{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchRequest is a request to search for packages
|
|
||||||
type SearchRequest struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`
|
|
||||||
Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
|
|
||||||
SortBy SORT_BY `protobuf:"varint,3,opt,name=sort_by,json=sortBy,proto3,enum=lure.SORT_BY" json:"sort_by,omitempty"`
|
|
||||||
FilterType FILTER_TYPE `protobuf:"varint,4,opt,name=filter_type,json=filterType,proto3,enum=lure.FILTER_TYPE" json:"filter_type,omitempty"`
|
|
||||||
FilterValue *string `protobuf:"bytes,5,opt,name=filter_value,json=filterValue,proto3,oneof" json:"filter_value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchRequest) Reset() {
|
|
||||||
*x = SearchRequest{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_lure_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*SearchRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *SearchRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_lure_proto_msgTypes[0]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use SearchRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*SearchRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_lure_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchRequest) GetQuery() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Query
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchRequest) GetLimit() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Limit
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchRequest) GetSortBy() SORT_BY {
|
|
||||||
if x != nil {
|
|
||||||
return x.SortBy
|
|
||||||
}
|
|
||||||
return SORT_BY_UNSORTED
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchRequest) GetFilterType() FILTER_TYPE {
|
|
||||||
if x != nil {
|
|
||||||
return x.FilterType
|
|
||||||
}
|
|
||||||
return FILTER_TYPE_NO_FILTER
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchRequest) GetFilterValue() string {
|
|
||||||
if x != nil && x.FilterValue != nil {
|
|
||||||
return *x.FilterValue
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringList contains a list of strings
|
|
||||||
type StringList struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Entries []string `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StringList) Reset() {
|
|
||||||
*x = StringList{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_lure_proto_msgTypes[1]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StringList) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*StringList) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *StringList) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_lure_proto_msgTypes[1]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use StringList.ProtoReflect.Descriptor instead.
|
|
||||||
func (*StringList) Descriptor() ([]byte, []int) {
|
|
||||||
return file_lure_proto_rawDescGZIP(), []int{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *StringList) GetEntries() []string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Entries
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package represents a LURE package
|
|
||||||
type Package struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
|
||||||
Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"`
|
|
||||||
Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
|
|
||||||
Release int64 `protobuf:"varint,4,opt,name=release,proto3" json:"release,omitempty"`
|
|
||||||
Epoch *int64 `protobuf:"varint,5,opt,name=epoch,proto3,oneof" json:"epoch,omitempty"`
|
|
||||||
Description *string `protobuf:"bytes,6,opt,name=description,proto3,oneof" json:"description,omitempty"`
|
|
||||||
Homepage *string `protobuf:"bytes,7,opt,name=homepage,proto3,oneof" json:"homepage,omitempty"`
|
|
||||||
Maintainer *string `protobuf:"bytes,8,opt,name=maintainer,proto3,oneof" json:"maintainer,omitempty"`
|
|
||||||
Architectures []string `protobuf:"bytes,9,rep,name=architectures,proto3" json:"architectures,omitempty"`
|
|
||||||
Licenses []string `protobuf:"bytes,10,rep,name=licenses,proto3" json:"licenses,omitempty"`
|
|
||||||
Provides []string `protobuf:"bytes,11,rep,name=provides,proto3" json:"provides,omitempty"`
|
|
||||||
Conflicts []string `protobuf:"bytes,12,rep,name=conflicts,proto3" json:"conflicts,omitempty"`
|
|
||||||
Replaces []string `protobuf:"bytes,13,rep,name=replaces,proto3" json:"replaces,omitempty"`
|
|
||||||
Depends map[string]*StringList `protobuf:"bytes,14,rep,name=depends,proto3" json:"depends,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
|
||||||
BuildDepends map[string]*StringList `protobuf:"bytes,15,rep,name=build_depends,json=buildDepends,proto3" json:"build_depends,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) Reset() {
|
|
||||||
*x = Package{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_lure_proto_msgTypes[2]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Package) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Package) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_lure_proto_msgTypes[2]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Package.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Package) Descriptor() ([]byte, []int) {
|
|
||||||
return file_lure_proto_rawDescGZIP(), []int{2}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetName() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetRepository() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Repository
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetVersion() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Version
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetRelease() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Release
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetEpoch() int64 {
|
|
||||||
if x != nil && x.Epoch != nil {
|
|
||||||
return *x.Epoch
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetDescription() string {
|
|
||||||
if x != nil && x.Description != nil {
|
|
||||||
return *x.Description
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetHomepage() string {
|
|
||||||
if x != nil && x.Homepage != nil {
|
|
||||||
return *x.Homepage
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetMaintainer() string {
|
|
||||||
if x != nil && x.Maintainer != nil {
|
|
||||||
return *x.Maintainer
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetArchitectures() []string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Architectures
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetLicenses() []string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Licenses
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetProvides() []string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Provides
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetConflicts() []string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Conflicts
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetReplaces() []string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Replaces
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetDepends() map[string]*StringList {
|
|
||||||
if x != nil {
|
|
||||||
return x.Depends
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Package) GetBuildDepends() map[string]*StringList {
|
|
||||||
if x != nil {
|
|
||||||
return x.BuildDepends
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetPackageRequest struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
|
||||||
Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetPackageRequest) Reset() {
|
|
||||||
*x = GetPackageRequest{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_lure_proto_msgTypes[3]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetPackageRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*GetPackageRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *GetPackageRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_lure_proto_msgTypes[3]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use GetPackageRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*GetPackageRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_lure_proto_rawDescGZIP(), []int{3}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetPackageRequest) GetName() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetPackageRequest) GetRepository() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Repository
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchResponse contains returned packages
|
|
||||||
type SearchResponse struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Packages []*Package `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchResponse) Reset() {
|
|
||||||
*x = SearchResponse{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_lure_proto_msgTypes[4]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*SearchResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *SearchResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_lure_proto_msgTypes[4]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use SearchResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*SearchResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_lure_proto_rawDescGZIP(), []int{4}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SearchResponse) GetPackages() []*Package {
|
|
||||||
if x != nil {
|
|
||||||
return x.Packages
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetBuildScriptRequest struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
|
||||||
Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetBuildScriptRequest) Reset() {
|
|
||||||
*x = GetBuildScriptRequest{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_lure_proto_msgTypes[5]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetBuildScriptRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*GetBuildScriptRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *GetBuildScriptRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_lure_proto_msgTypes[5]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use GetBuildScriptRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*GetBuildScriptRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_lure_proto_rawDescGZIP(), []int{5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetBuildScriptRequest) GetName() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetBuildScriptRequest) GetRepository() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Repository
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetBuildScriptResponse struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Script string `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetBuildScriptResponse) Reset() {
|
|
||||||
*x = GetBuildScriptResponse{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_lure_proto_msgTypes[6]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetBuildScriptResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*GetBuildScriptResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *GetBuildScriptResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_lure_proto_msgTypes[6]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use GetBuildScriptResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*GetBuildScriptResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_lure_proto_rawDescGZIP(), []int{6}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetBuildScriptResponse) GetScript() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Script
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_lure_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
var file_lure_proto_rawDesc = []byte{
|
|
||||||
0x0a, 0x0a, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6c, 0x75,
|
|
||||||
0x72, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71,
|
|
||||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69,
|
|
||||||
0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74,
|
|
||||||
0x12, 0x26, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
|
|
||||||
0x0e, 0x32, 0x0d, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59,
|
|
||||||
0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x32, 0x0a, 0x0b, 0x66, 0x69, 0x6c, 0x74,
|
|
||||||
0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e,
|
|
||||||
0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45,
|
|
||||||
0x52, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0c,
|
|
||||||
0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01,
|
|
||||||
0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75,
|
|
||||||
0x65, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f,
|
|
||||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c,
|
|
||||||
0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01,
|
|
||||||
0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xe4, 0x05,
|
|
||||||
0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
|
|
||||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a,
|
|
||||||
0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x0a,
|
|
||||||
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
|
||||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61,
|
|
||||||
0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
|
|
||||||
0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03,
|
|
||||||
0x48, 0x00, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b,
|
|
||||||
0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
|
||||||
0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67, 0x65, 0x18,
|
|
||||||
0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67,
|
|
||||||
0x65, 0x88, 0x01, 0x01, 0x12, 0x23, 0x0a, 0x0a, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
|
||||||
0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0a, 0x6d, 0x61, 0x69, 0x6e,
|
|
||||||
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x72, 0x63,
|
|
||||||
0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09,
|
|
||||||
0x52, 0x0d, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12,
|
|
||||||
0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28,
|
|
||||||
0x09, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70,
|
|
||||||
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70,
|
|
||||||
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c,
|
|
||||||
0x69, 0x63, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66,
|
|
||||||
0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
|
|
||||||
0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
|
|
||||||
0x73, 0x12, 0x34, 0x0a, 0x07, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x03,
|
|
||||||
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67,
|
|
||||||
0x65, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07,
|
|
||||||
0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x44, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64,
|
|
||||||
0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f,
|
|
||||||
0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x2e, 0x42, 0x75,
|
|
||||||
0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
|
|
||||||
0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x1a, 0x4c, 0x0a,
|
|
||||||
0x0c, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
|
|
||||||
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
|
|
||||||
0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10,
|
|
||||||
0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74,
|
|
||||||
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x51, 0x0a, 0x11, 0x42,
|
|
||||||
0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
|
||||||
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
|
|
||||||
0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
|
||||||
0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c,
|
|
||||||
0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x08,
|
|
||||||
0x0a, 0x06, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73,
|
|
||||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x68, 0x6f, 0x6d,
|
|
||||||
0x65, 0x70, 0x61, 0x67, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61,
|
|
||||||
0x69, 0x6e, 0x65, 0x72, 0x22, 0x47, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x61,
|
|
||||||
0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
|
|
||||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a,
|
|
||||||
0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x3b, 0x0a,
|
|
||||||
0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
|
||||||
0x29, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
|
|
||||||
0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
|
|
||||||
0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x15, 0x47, 0x65,
|
|
||||||
0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75,
|
|
||||||
0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73,
|
|
||||||
0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70,
|
|
||||||
0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x30, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x75,
|
|
||||||
0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
|
||||||
0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2a, 0x3e, 0x0a, 0x07, 0x53, 0x4f, 0x52,
|
|
||||||
0x54, 0x5f, 0x42, 0x59, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x4e, 0x53, 0x4f, 0x52, 0x54, 0x45, 0x44,
|
|
||||||
0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a,
|
|
||||||
0x52, 0x45, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07,
|
|
||||||
0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x2a, 0x42, 0x0a, 0x0b, 0x46, 0x49, 0x4c,
|
|
||||||
0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x46,
|
|
||||||
0x49, 0x4c, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x5f, 0x52, 0x45,
|
|
||||||
0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x55,
|
|
||||||
0x50, 0x50, 0x4f, 0x52, 0x54, 0x53, 0x5f, 0x41, 0x52, 0x43, 0x48, 0x10, 0x02, 0x32, 0xb9, 0x01,
|
|
||||||
0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x33, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12,
|
|
||||||
0x13, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71,
|
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72,
|
|
||||||
0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x47, 0x65,
|
|
||||||
0x74, 0x50, 0x6b, 0x67, 0x12, 0x17, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x50,
|
|
||||||
0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e,
|
|
||||||
0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x4b, 0x0a, 0x0e,
|
|
||||||
0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1b,
|
|
||||||
0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63,
|
|
||||||
0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x75,
|
|
||||||
0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70,
|
|
||||||
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2e, 0x2f,
|
|
||||||
0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_lure_proto_rawDescOnce sync.Once
|
|
||||||
file_lure_proto_rawDescData = file_lure_proto_rawDesc
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_lure_proto_rawDescGZIP() []byte {
|
|
||||||
file_lure_proto_rawDescOnce.Do(func() {
|
|
||||||
file_lure_proto_rawDescData = protoimpl.X.CompressGZIP(file_lure_proto_rawDescData)
|
|
||||||
})
|
|
||||||
return file_lure_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_lure_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
|
||||||
var file_lure_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
|
||||||
var file_lure_proto_goTypes = []interface{}{
|
|
||||||
(SORT_BY)(0), // 0: lure.SORT_BY
|
|
||||||
(FILTER_TYPE)(0), // 1: lure.FILTER_TYPE
|
|
||||||
(*SearchRequest)(nil), // 2: lure.SearchRequest
|
|
||||||
(*StringList)(nil), // 3: lure.StringList
|
|
||||||
(*Package)(nil), // 4: lure.Package
|
|
||||||
(*GetPackageRequest)(nil), // 5: lure.GetPackageRequest
|
|
||||||
(*SearchResponse)(nil), // 6: lure.SearchResponse
|
|
||||||
(*GetBuildScriptRequest)(nil), // 7: lure.GetBuildScriptRequest
|
|
||||||
(*GetBuildScriptResponse)(nil), // 8: lure.GetBuildScriptResponse
|
|
||||||
nil, // 9: lure.Package.DependsEntry
|
|
||||||
nil, // 10: lure.Package.BuildDependsEntry
|
|
||||||
}
|
|
||||||
var file_lure_proto_depIdxs = []int32{
|
|
||||||
0, // 0: lure.SearchRequest.sort_by:type_name -> lure.SORT_BY
|
|
||||||
1, // 1: lure.SearchRequest.filter_type:type_name -> lure.FILTER_TYPE
|
|
||||||
9, // 2: lure.Package.depends:type_name -> lure.Package.DependsEntry
|
|
||||||
10, // 3: lure.Package.build_depends:type_name -> lure.Package.BuildDependsEntry
|
|
||||||
4, // 4: lure.SearchResponse.packages:type_name -> lure.Package
|
|
||||||
3, // 5: lure.Package.DependsEntry.value:type_name -> lure.StringList
|
|
||||||
3, // 6: lure.Package.BuildDependsEntry.value:type_name -> lure.StringList
|
|
||||||
2, // 7: lure.API.Search:input_type -> lure.SearchRequest
|
|
||||||
5, // 8: lure.API.GetPkg:input_type -> lure.GetPackageRequest
|
|
||||||
7, // 9: lure.API.GetBuildScript:input_type -> lure.GetBuildScriptRequest
|
|
||||||
6, // 10: lure.API.Search:output_type -> lure.SearchResponse
|
|
||||||
4, // 11: lure.API.GetPkg:output_type -> lure.Package
|
|
||||||
8, // 12: lure.API.GetBuildScript:output_type -> lure.GetBuildScriptResponse
|
|
||||||
10, // [10:13] is the sub-list for method output_type
|
|
||||||
7, // [7:10] is the sub-list for method input_type
|
|
||||||
7, // [7:7] is the sub-list for extension type_name
|
|
||||||
7, // [7:7] is the sub-list for extension extendee
|
|
||||||
0, // [0:7] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_lure_proto_init() }
|
|
||||||
func file_lure_proto_init() {
|
|
||||||
if File_lure_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_lure_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*SearchRequest); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_lure_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*StringList); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_lure_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*Package); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_lure_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*GetPackageRequest); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_lure_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*SearchResponse); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_lure_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*GetBuildScriptRequest); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_lure_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*GetBuildScriptResponse); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_lure_proto_msgTypes[0].OneofWrappers = []interface{}{}
|
|
||||||
file_lure_proto_msgTypes[2].OneofWrappers = []interface{}{}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: file_lure_proto_rawDesc,
|
|
||||||
NumEnums: 2,
|
|
||||||
NumMessages: 9,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 1,
|
|
||||||
},
|
|
||||||
GoTypes: file_lure_proto_goTypes,
|
|
||||||
DependencyIndexes: file_lure_proto_depIdxs,
|
|
||||||
EnumInfos: file_lure_proto_enumTypes,
|
|
||||||
MessageInfos: file_lure_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_lure_proto = out.File
|
|
||||||
file_lure_proto_rawDesc = nil
|
|
||||||
file_lure_proto_goTypes = nil
|
|
||||||
file_lure_proto_depIdxs = nil
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
package lure;
|
|
||||||
|
|
||||||
// Slight hack to provide protoc with a package name
|
|
||||||
option go_package = "../api";
|
|
||||||
|
|
||||||
// SORT_BY represents possible things to sort packages by
|
|
||||||
enum SORT_BY {
|
|
||||||
UNSORTED = 0;
|
|
||||||
NAME = 1;
|
|
||||||
REPOSITORY = 2;
|
|
||||||
VERSION = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FILTER_TYPE represents possible filters for packages
|
|
||||||
enum FILTER_TYPE {
|
|
||||||
NO_FILTER = 0;
|
|
||||||
IN_REPOSITORY = 1;
|
|
||||||
SUPPORTS_ARCH = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchRequest is a request to search for packages
|
|
||||||
message SearchRequest {
|
|
||||||
string query = 1;
|
|
||||||
int64 limit = 2;
|
|
||||||
SORT_BY sort_by = 3;
|
|
||||||
FILTER_TYPE filter_type = 4;
|
|
||||||
optional string filter_value = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringList contains a list of strings
|
|
||||||
message StringList {
|
|
||||||
repeated string entries = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package represents a LURE package
|
|
||||||
message Package {
|
|
||||||
string name = 1;
|
|
||||||
string repository = 2;
|
|
||||||
string version = 3;
|
|
||||||
int64 release = 4;
|
|
||||||
optional int64 epoch = 5;
|
|
||||||
optional string description = 6;
|
|
||||||
optional string homepage = 7;
|
|
||||||
optional string maintainer = 8;
|
|
||||||
repeated string architectures = 9;
|
|
||||||
repeated string licenses = 10;
|
|
||||||
repeated string provides = 11;
|
|
||||||
repeated string conflicts = 12;
|
|
||||||
repeated string replaces = 13;
|
|
||||||
map<string, StringList> depends = 14;
|
|
||||||
map<string, StringList> build_depends = 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetPackageRequest {
|
|
||||||
string name = 1;
|
|
||||||
string repository = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchResponse contains returned packages
|
|
||||||
message SearchResponse {
|
|
||||||
repeated Package packages = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetBuildScriptRequest {
|
|
||||||
string name = 1;
|
|
||||||
string repository = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetBuildScriptResponse {
|
|
||||||
string script = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Web is the LURE Web service
|
|
||||||
service API {
|
|
||||||
// Search searches through LURE packages in the database
|
|
||||||
rpc Search(SearchRequest) returns (SearchResponse);
|
|
||||||
// GetPkg gets a single LURE package from the database
|
|
||||||
rpc GetPkg(GetPackageRequest) returns (Package);
|
|
||||||
// GetBuildScript returns the build script for the given package
|
|
||||||
rpc GetBuildScript(GetBuildScriptRequest) returns (GetBuildScriptResponse);
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,32 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package cliutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"go.arsenm.dev/logger/log"
|
"lure.sh/lure/internal/config"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/internal/db"
|
||||||
"go.arsenm.dev/lure/internal/pager"
|
"lure.sh/lure/internal/pager"
|
||||||
|
"lure.sh/lure/internal/translations"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// YesNoPrompt asks the user a yes or no question, using def as the default answer
|
// 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(ctx context.Context, msg string, interactive, def bool) (bool, error) {
|
||||||
var answer bool
|
if interactive {
|
||||||
err := survey.AskOne(
|
var answer bool
|
||||||
&survey.Confirm{
|
err := survey.AskOne(
|
||||||
Message: msg,
|
&survey.Confirm{
|
||||||
Default: def,
|
Message: translations.Translator(ctx).TranslateTo(msg, config.Language(ctx)),
|
||||||
},
|
Default: def,
|
||||||
&answer,
|
},
|
||||||
)
|
&answer,
|
||||||
return answer, err
|
)
|
||||||
|
return answer, err
|
||||||
|
} else {
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PromptViewScript asks the user if they'd like to see a script,
|
// 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
|
// shows it if they answer yes, then asks if they'd still like to
|
||||||
// continue, and exits if they answer no.
|
// continue, and exits if they answer no.
|
||||||
func PromptViewScript(script, name, style string) error {
|
func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error {
|
||||||
view, err := YesNoPrompt("Would you like to view the build script for "+name, false)
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
|
if !interactive {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPrompt := translations.Translator(ctx).TranslateTo("Would you like to view the build script for", config.Language(ctx)) + " " + name
|
||||||
|
view, err := YesNoPrompt(ctx, scriptPrompt, interactive, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -37,13 +70,13 @@ func PromptViewScript(script, name, style string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cont, err := YesNoPrompt("Would you still like to continue?", false)
|
cont, err := YesNoPrompt(ctx, "Would you still like to continue?", interactive, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cont {
|
if !cont {
|
||||||
log.Fatal("User chose not to continue after reading script").Send()
|
log.Fatal(translations.Translator(ctx).TranslateTo("User chose not to continue after reading script", config.Language(ctx))).Send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,16 +103,17 @@ func ShowScript(path, name, style string) error {
|
|||||||
|
|
||||||
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
|
// 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.
|
// of packages by prompting the user if multiple packages match.
|
||||||
func FlattenPkgs(found map[string][]db.Package, verb string) []db.Package {
|
func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
var outPkgs []db.Package
|
var outPkgs []db.Package
|
||||||
for _, pkgs := range found {
|
for _, pkgs := range found {
|
||||||
if len(pkgs) > 1 {
|
if len(pkgs) > 1 && interactive {
|
||||||
choices, err := PkgPrompt(pkgs, verb)
|
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error prompting for choice of package").Send()
|
log.Fatal("Error prompting for choice of package").Send()
|
||||||
}
|
}
|
||||||
outPkgs = append(outPkgs, choices...)
|
outPkgs = append(outPkgs, choice)
|
||||||
} else if len(pkgs) == 1 {
|
} else if len(pkgs) == 1 || !interactive {
|
||||||
outPkgs = append(outPkgs, pkgs[0])
|
outPkgs = append(outPkgs, pkgs[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,16 +121,40 @@ func FlattenPkgs(found map[string][]db.Package, verb string) []db.Package {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PkgPrompt asks the user to choose between multiple packages.
|
// PkgPrompt asks the user to choose between multiple packages.
|
||||||
// The user may choose multiple packages.
|
func PkgPrompt(ctx context.Context, options []db.Package, verb string, interactive bool) (db.Package, error) {
|
||||||
func PkgPrompt(options []db.Package, verb string) ([]db.Package, error) {
|
if !interactive {
|
||||||
|
return options[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
names := make([]string, len(options))
|
names := make([]string, len(options))
|
||||||
for i, option := range options {
|
for i, option := range options {
|
||||||
names[i] = option.Repository + "/" + option.Name + " " + option.Version
|
names[i] = option.Repository + "/" + option.Name + " " + option.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt := &survey.MultiSelect{
|
prompt := &survey.Select{
|
||||||
Options: names,
|
Options: names,
|
||||||
Message: "Choose which package(s) to " + verb,
|
Message: translations.Translator(ctx).TranslateTo("Choose which package to "+verb, config.Language(ctx)),
|
||||||
|
}
|
||||||
|
|
||||||
|
var choice int
|
||||||
|
err := survey.AskOne(prompt, &choice)
|
||||||
|
if err != nil {
|
||||||
|
return db.Package{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return options[choice], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChooseOptDepends asks the user to choose between multiple optional dependencies.
|
||||||
|
// The user may choose multiple items.
|
||||||
|
func ChooseOptDepends(ctx context.Context, options []string, verb string, interactive bool) ([]string, error) {
|
||||||
|
if !interactive {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt := &survey.MultiSelect{
|
||||||
|
Options: options,
|
||||||
|
Message: translations.Translator(ctx).TranslateTo("Choose which optional package(s) to install", config.Language(ctx)),
|
||||||
}
|
}
|
||||||
|
|
||||||
var choices []int
|
var choices []int
|
||||||
@ -105,9 +163,9 @@ func PkgPrompt(options []db.Package, verb string) ([]db.Package, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]db.Package, len(choices))
|
out := make([]string, len(choices))
|
||||||
for i, choiceIndex := range choices {
|
for i, choiceIndex := range choices {
|
||||||
out[i] = options[choiceIndex]
|
out[i], _, _ = strings.Cut(options[choiceIndex], ": ")
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
|
@ -1,36 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
"go.arsenm.dev/lure/internal/types"
|
"lure.sh/lure/internal/types"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultConfig = types.Config{
|
var defaultConfig = &types.Config{
|
||||||
RootCmd: "sudo",
|
RootCmd: "sudo",
|
||||||
PagerStyle: "native",
|
PagerStyle: "native",
|
||||||
IgnorePkgUpdates: []string{},
|
IgnorePkgUpdates: []string{},
|
||||||
Repos: []types.Repo{
|
Repos: []types.Repo{
|
||||||
{
|
{
|
||||||
Name: "default",
|
Name: "default",
|
||||||
URL: "https://github.com/Arsen6331/lure-repo.git",
|
URL: "https://github.com/lure-sh/lure-repo.git",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode decodes the config file into the given
|
var (
|
||||||
// pointer
|
configMtx sync.Mutex
|
||||||
func Decode(cfg *types.Config) error {
|
config *types.Config
|
||||||
cfgFl, err := os.Open(ConfigPath)
|
)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cfgFl.Close()
|
|
||||||
|
|
||||||
// Write defaults to pointer in case some values are not set in the config
|
// Config returns a LURE configuration struct.
|
||||||
*cfg = defaultConfig
|
// The first time it's called, it'll load the config from a file.
|
||||||
// Set repos to nil so as to avoid a duplicate default
|
// Subsequent calls will just return the same value.
|
||||||
cfg.Repos = nil
|
func Config(ctx context.Context) *types.Config {
|
||||||
return toml.NewDecoder(cfgFl).Decode(cfg)
|
configMtx.Lock()
|
||||||
|
defer configMtx.Unlock()
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
|
if config == nil {
|
||||||
|
cfgFl, err := os.Open(GetPaths(ctx).ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error opening config file, using defaults").Err(err).Send()
|
||||||
|
return defaultConfig
|
||||||
|
}
|
||||||
|
defer cfgFl.Close()
|
||||||
|
|
||||||
|
// Copy the default configuration into config
|
||||||
|
defCopy := *defaultConfig
|
||||||
|
config = &defCopy
|
||||||
|
config.Repos = nil
|
||||||
|
|
||||||
|
err = toml.NewDecoder(cfgFl).Decode(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error decoding config file, using defaults").Err(err).Send()
|
||||||
|
// Set config back to nil so that we try again next time
|
||||||
|
config = nil
|
||||||
|
return defaultConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
}
|
}
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
|
||||||
"go.arsenm.dev/logger/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ConfigDir string
|
|
||||||
ConfigPath string
|
|
||||||
CacheDir string
|
|
||||||
RepoDir string
|
|
||||||
PkgsDir string
|
|
||||||
DBPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
// DBPresent is true if the database
|
|
||||||
// was present when LURE was started
|
|
||||||
var DBPresent bool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cfgDir, err := os.UserConfigDir()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to detect user config directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigDir = filepath.Join(cfgDir, "lure")
|
|
||||||
|
|
||||||
err = os.MkdirAll(ConfigDir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to create LURE config directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigPath = filepath.Join(ConfigDir, "lure.toml")
|
|
||||||
|
|
||||||
if _, err := os.Stat(ConfigPath); err != nil {
|
|
||||||
cfgFl, err := os.Create(ConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to create LURE config file").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error encoding default configuration").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
cfgFl.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheDir, err := os.UserCacheDir()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to detect cache directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
CacheDir = filepath.Join(cacheDir, "lure")
|
|
||||||
RepoDir = filepath.Join(CacheDir, "repo")
|
|
||||||
PkgsDir = filepath.Join(CacheDir, "pkgs")
|
|
||||||
|
|
||||||
err = os.MkdirAll(RepoDir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to create repo cache directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(PkgsDir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to create package cache directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
DBPath = filepath.Join(CacheDir, "db")
|
|
||||||
|
|
||||||
_, err = os.ReadDir(DBPath)
|
|
||||||
DBPresent = err == nil
|
|
||||||
}
|
|
67
internal/config/lang.go
Normal file
67
internal/config/lang.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
langMtx sync.Mutex
|
||||||
|
lang language.Tag
|
||||||
|
langSet bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// Language returns the system language.
|
||||||
|
// The first time it's called, it'll detect the langauge based on
|
||||||
|
// the $LANG environment variable.
|
||||||
|
// Subsequent calls will just return the same value.
|
||||||
|
func Language(ctx context.Context) language.Tag {
|
||||||
|
langMtx.Lock()
|
||||||
|
defer langMtx.Unlock()
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
if !langSet {
|
||||||
|
syslang := SystemLang()
|
||||||
|
tag, err := language.Parse(syslang)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error parsing system language").Err(err).Send()
|
||||||
|
}
|
||||||
|
base, _ := tag.Base()
|
||||||
|
lang = language.Make(base.String())
|
||||||
|
langSet = true
|
||||||
|
}
|
||||||
|
return lang
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemLang returns the system language based on
|
||||||
|
// the $LANG environment variable.
|
||||||
|
func SystemLang() string {
|
||||||
|
lang := os.Getenv("LANG")
|
||||||
|
lang, _, _ = strings.Cut(lang, ".")
|
||||||
|
if lang == "" || lang == "C" {
|
||||||
|
lang = "en"
|
||||||
|
}
|
||||||
|
return lang
|
||||||
|
}
|
108
internal/config/paths.go
Normal file
108
internal/config/paths.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Paths contains various paths used by LURE
|
||||||
|
type Paths struct {
|
||||||
|
ConfigDir string
|
||||||
|
ConfigPath string
|
||||||
|
CacheDir string
|
||||||
|
RepoDir string
|
||||||
|
PkgsDir string
|
||||||
|
DBPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
pathsMtx sync.Mutex
|
||||||
|
paths *Paths
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPaths returns a Paths struct.
|
||||||
|
// The first time it's called, it'll generate the struct
|
||||||
|
// using information from the system.
|
||||||
|
// Subsequent calls will return the same value.
|
||||||
|
func GetPaths(ctx context.Context) *Paths {
|
||||||
|
pathsMtx.Lock()
|
||||||
|
defer pathsMtx.Unlock()
|
||||||
|
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
if paths == nil {
|
||||||
|
paths = &Paths{}
|
||||||
|
|
||||||
|
cfgDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to detect user config directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.ConfigDir = filepath.Join(cfgDir, "lure")
|
||||||
|
|
||||||
|
err = os.MkdirAll(paths.ConfigDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to create LURE config directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.ConfigPath = filepath.Join(paths.ConfigDir, "lure.toml")
|
||||||
|
|
||||||
|
if _, err := os.Stat(paths.ConfigPath); err != nil {
|
||||||
|
cfgFl, err := os.Create(paths.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to create LURE config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error encoding default configuration").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgFl.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheDir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to detect cache directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.CacheDir = filepath.Join(cacheDir, "lure")
|
||||||
|
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
|
||||||
|
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
|
||||||
|
|
||||||
|
err = os.MkdirAll(paths.RepoDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to create repo cache directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(paths.PkgsDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to create package cache directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.DBPath = filepath.Join(paths.CacheDir, "db")
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import _ "embed"
|
// Version contains the version of LURE. If the version
|
||||||
|
// isn't known, it'll be set to "unknown"
|
||||||
//go:embed version.txt
|
var Version = "unknown"
|
||||||
var Version string
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -21,14 +21,16 @@ package cpu
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/sys/cpu"
|
"golang.org/x/sys/cpu"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ARMVariant checks which variant of ARM lure is running
|
// armVariant checks which variant of ARM lure is running
|
||||||
// on, by using the same detection method as Go itself
|
// on, by using the same detection method as Go itself
|
||||||
func ARMVariant() string {
|
func armVariant() string {
|
||||||
armEnv := os.Getenv("LURE_ARM_VARIANT")
|
armEnv := os.Getenv("LURE_ARM_VARIANT")
|
||||||
// ensure value has "arm" prefix, such as arm5 or arm6
|
// ensure value has "arm" prefix, such as arm5 or arm6
|
||||||
if strings.HasPrefix(armEnv, "arm") {
|
if strings.HasPrefix(armEnv, "arm") {
|
||||||
@ -46,9 +48,70 @@ func ARMVariant() string {
|
|||||||
|
|
||||||
// Arch returns the canonical CPU architecture of the system
|
// Arch returns the canonical CPU architecture of the system
|
||||||
func Arch() string {
|
func Arch() string {
|
||||||
arch := runtime.GOARCH
|
arch := os.Getenv("LURE_ARCH")
|
||||||
|
if arch == "" {
|
||||||
|
arch = runtime.GOARCH
|
||||||
|
}
|
||||||
if arch == "arm" {
|
if arch == "arm" {
|
||||||
arch = ARMVariant()
|
arch = armVariant()
|
||||||
}
|
}
|
||||||
return arch
|
return arch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsCompatibleWith(target string, list []string) bool {
|
||||||
|
if target == "all" || slices.Contains(list, "all") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arch := range list {
|
||||||
|
if strings.HasPrefix(target, "arm") && strings.HasPrefix(arch, "arm") {
|
||||||
|
targetVer, err := getARMVersion(target)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
archVer, err := getARMVersion(arch)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetVer >= archVer {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target == arch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompatibleArches(arch string) ([]string, error) {
|
||||||
|
if strings.HasPrefix(arch, "arm") {
|
||||||
|
ver, err := getARMVersion(arch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ver > 5 {
|
||||||
|
var out []string
|
||||||
|
for i := ver; i >= 5; i-- {
|
||||||
|
out = append(out, "arm"+strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{arch}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getARMVersion(arch string) (int, error) {
|
||||||
|
// Extract the version number from ARM architecture
|
||||||
|
version := strings.TrimPrefix(arch, "arm")
|
||||||
|
if version == "" {
|
||||||
|
return 5, nil // Default to arm5 if version is not specified
|
||||||
|
}
|
||||||
|
return strconv.Atoi(version)
|
||||||
|
}
|
||||||
|
@ -1,19 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"lure.sh/lure/internal/config"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"modernc.org/sqlite"
|
"modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CurrentVersion is the current version of the database.
|
||||||
|
// The database is reset if its version doesn't match this.
|
||||||
|
const CurrentVersion = 2
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sqlite.MustRegisterScalarFunction("json_array_contains", 2, JsonArrayContains)
|
sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package is a LURE package's database representation
|
// Package is a LURE package's database representation
|
||||||
@ -22,9 +48,9 @@ type Package struct {
|
|||||||
Version string `sh:"version,required" db:"version"`
|
Version string `sh:"version,required" db:"version"`
|
||||||
Release int `sh:"release,required" db:"release"`
|
Release int `sh:"release,required" db:"release"`
|
||||||
Epoch uint `sh:"epoch" db:"epoch"`
|
Epoch uint `sh:"epoch" db:"epoch"`
|
||||||
Description string `sh:"desc" db:"description"`
|
Description JSON[map[string]string] `db:"description"`
|
||||||
Homepage string `sh:"homepage" db:"homepage"`
|
Homepage JSON[map[string]string] `db:"homepage"`
|
||||||
Maintainer string `sh:"maintainer" db:"maintainer"`
|
Maintainer JSON[map[string]string] `db:"maintainer"`
|
||||||
Architectures JSON[[]string] `sh:"architectures" db:"architectures"`
|
Architectures JSON[[]string] `sh:"architectures" db:"architectures"`
|
||||||
Licenses JSON[[]string] `sh:"license" db:"licenses"`
|
Licenses JSON[[]string] `sh:"license" db:"licenses"`
|
||||||
Provides JSON[[]string] `sh:"provides" db:"provides"`
|
Provides JSON[[]string] `sh:"provides" db:"provides"`
|
||||||
@ -32,22 +58,84 @@ type Package struct {
|
|||||||
Replaces JSON[[]string] `sh:"replaces" db:"replaces"`
|
Replaces JSON[[]string] `sh:"replaces" db:"replaces"`
|
||||||
Depends JSON[map[string][]string] `db:"depends"`
|
Depends JSON[map[string][]string] `db:"depends"`
|
||||||
BuildDepends JSON[map[string][]string] `db:"builddepends"`
|
BuildDepends JSON[map[string][]string] `db:"builddepends"`
|
||||||
|
OptDepends JSON[map[string][]string] `db:"optdepends"`
|
||||||
Repository string `db:"repository"`
|
Repository string `db:"repository"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the database
|
type version struct {
|
||||||
func Init(db *sqlx.DB) error {
|
Version int `db:"version"`
|
||||||
*db = *db.Unsafe()
|
}
|
||||||
_, err := db.Exec(`
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
conn *sqlx.DB
|
||||||
|
closed = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// DB returns the LURE database.
|
||||||
|
// The first time it's called, it opens the SQLite database file.
|
||||||
|
// Subsequent calls return the same connection.
|
||||||
|
func DB(ctx context.Context) *sqlx.DB {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
if conn != nil && !closed {
|
||||||
|
return getConn()
|
||||||
|
}
|
||||||
|
_, err := open(ctx, config.GetPaths(ctx).DBPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error opening database").Err(err).Send()
|
||||||
|
}
|
||||||
|
return getConn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConn() *sqlx.DB {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func open(ctx context.Context, dsn string) (*sqlx.DB, error) {
|
||||||
|
db, err := sqlx.Open("sqlite", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
conn = db
|
||||||
|
closed = false
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
err = initDB(ctx, dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the database
|
||||||
|
func Close() error {
|
||||||
|
closed = true
|
||||||
|
if conn != nil {
|
||||||
|
return conn.Close()
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initDB initializes the database
|
||||||
|
func initDB(ctx context.Context, dsn string) error {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
conn = conn.Unsafe()
|
||||||
|
_, err := conn.ExecContext(ctx, `
|
||||||
CREATE TABLE IF NOT EXISTS pkgs (
|
CREATE TABLE IF NOT EXISTS pkgs (
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
repository TEXT NOT NULL,
|
repository TEXT NOT NULL,
|
||||||
version TEXT NOT NULL,
|
version TEXT NOT NULL,
|
||||||
release INT NOT NULL,
|
release INT NOT NULL,
|
||||||
epoch INT,
|
epoch INT,
|
||||||
description TEXT,
|
description TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')),
|
||||||
homepage TEXT,
|
homepage TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')),
|
||||||
maintainer TEXT,
|
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')),
|
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')),
|
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')),
|
provides TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')),
|
||||||
@ -55,15 +143,70 @@ func Init(db *sqlx.DB) error {
|
|||||||
replaces TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')),
|
replaces TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')),
|
||||||
depends TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')),
|
depends TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')),
|
||||||
builddepends TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')),
|
builddepends TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')),
|
||||||
|
optdepends TEXT CHECK(optdepends = 'null' OR (JSON_VALID(optdepends) AND JSON_TYPE(optdepends) = 'object')),
|
||||||
UNIQUE(name, repository)
|
UNIQUE(name, repository)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS lure_db_version (
|
||||||
|
version INT NOT NULL
|
||||||
|
);
|
||||||
`)
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ver, ok := GetVersion(ctx)
|
||||||
|
if ok && ver != CurrentVersion {
|
||||||
|
log.Warn("Database version mismatch; resetting").Int("version", ver).Int("expected", CurrentVersion).Send()
|
||||||
|
reset(ctx)
|
||||||
|
return initDB(ctx, dsn)
|
||||||
|
} else if !ok {
|
||||||
|
log.Warn("Database version does not exist. Run lure fix if something isn't working.").Send()
|
||||||
|
return addVersion(ctx, CurrentVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset drops all the database tables
|
||||||
|
func reset(ctx context.Context) error {
|
||||||
|
_, err := DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS lure_db_version;")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if the database has no packages in it, otherwise it returns false.
|
||||||
|
func IsEmpty(ctx context.Context) bool {
|
||||||
|
var count int
|
||||||
|
err := DB(ctx).GetContext(ctx, &count, "SELECT count(1) FROM pkgs;")
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return count == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion returns the database version and a boolean indicating
|
||||||
|
// whether the database contained a version number
|
||||||
|
func GetVersion(ctx context.Context) (int, bool) {
|
||||||
|
var ver version
|
||||||
|
err := DB(ctx).GetContext(ctx, &ver, "SELECT * FROM lure_db_version LIMIT 1;")
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return ver.Version, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVersion(ctx context.Context, ver int) error {
|
||||||
|
_, err := DB(ctx).ExecContext(ctx, `INSERT INTO lure_db_version(version) VALUES (?);`, ver)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertPackage adds a package to the database
|
// InsertPackage adds a package to the database
|
||||||
func InsertPackage(db *sqlx.DB, pkg Package) error {
|
func InsertPackage(ctx context.Context, pkg Package) error {
|
||||||
_, err := db.NamedExec(`
|
_, err := DB(ctx).NamedExecContext(ctx, `
|
||||||
INSERT OR REPLACE INTO pkgs (
|
INSERT OR REPLACE INTO pkgs (
|
||||||
name,
|
name,
|
||||||
repository,
|
repository,
|
||||||
@ -79,7 +222,8 @@ func InsertPackage(db *sqlx.DB, pkg Package) error {
|
|||||||
conflicts,
|
conflicts,
|
||||||
replaces,
|
replaces,
|
||||||
depends,
|
depends,
|
||||||
builddepends
|
builddepends,
|
||||||
|
optdepends
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:name,
|
:name,
|
||||||
:repository,
|
:repository,
|
||||||
@ -95,35 +239,38 @@ func InsertPackage(db *sqlx.DB, pkg Package) error {
|
|||||||
:conflicts,
|
:conflicts,
|
||||||
:replaces,
|
:replaces,
|
||||||
:depends,
|
:depends,
|
||||||
:builddepends
|
:builddepends,
|
||||||
|
:optdepends
|
||||||
);
|
);
|
||||||
`, pkg)
|
`, pkg)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPkgs returns a result containing packages that match the where conditions
|
// GetPkgs returns a result containing packages that match the where conditions
|
||||||
func GetPkgs(db *sqlx.DB, where string, args ...any) (*sqlx.Rows, error) {
|
func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) {
|
||||||
stream, err := db.Queryx("SELECT * FROM pkgs WHERE "+where, args...)
|
stream, err := DB(ctx).QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return stream, nil
|
return stream, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPkg returns a single package that match the where conditions
|
// GetPkg returns a single package that matches the where conditions
|
||||||
func GetPkg(db *sqlx.DB, where string, args ...any) (*Package, error) {
|
func GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
|
||||||
out := &Package{}
|
out := &Package{}
|
||||||
err := db.Get(out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
|
err := DB(ctx).GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePkgs deletes all packages matching the where conditions
|
// DeletePkgs deletes all packages matching the where conditions
|
||||||
func DeletePkgs(db *sqlx.DB, where string, args ...any) error {
|
func DeletePkgs(ctx context.Context, where string, args ...any) error {
|
||||||
_, err := db.Exec("DELETE FROM pkgs WHERE "+where, args...)
|
_, err := DB(ctx).ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func JsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
|
// jsonArrayContains is an SQLite function that checks if a JSON array
|
||||||
|
// in the database contains a given value
|
||||||
|
func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
|
||||||
value, ok := args[0].(string)
|
value, ok := args[0].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("both arguments to json_array_contains must be strings")
|
return nil, errors.New("both arguments to json_array_contains must be strings")
|
||||||
@ -143,10 +290,12 @@ func JsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver
|
|||||||
return slices.Contains(array, item), nil
|
return slices.Contains(array, item), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSON represents a JSON value in the database
|
||||||
type JSON[T any] struct {
|
type JSON[T any] struct {
|
||||||
Val T
|
Val T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSON creates a new database JSON value
|
||||||
func NewJSON[T any](v T) JSON[T] {
|
func NewJSON[T any](v T) JSON[T] {
|
||||||
return JSON[T]{Val: v}
|
return JSON[T]{Val: v}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package db_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -6,17 +24,25 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testPkg = db.Package{
|
var testPkg = db.Package{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Version: "0.0.1",
|
Version: "0.0.1",
|
||||||
Release: 1,
|
Release: 1,
|
||||||
Epoch: 2,
|
Epoch: 2,
|
||||||
Description: "Test package",
|
Description: db.NewJSON(map[string]string{
|
||||||
Homepage: "https://lure.arsenm.dev",
|
"en": "Test package",
|
||||||
Maintainer: "Arsen Musayelyan <arsen@arsenm.dev>",
|
"ru": "Проверочный пакет",
|
||||||
|
}),
|
||||||
|
Homepage: db.NewJSON(map[string]string{
|
||||||
|
"en": "https://lure.sh/",
|
||||||
|
}),
|
||||||
|
Maintainer: db.NewJSON(map[string]string{
|
||||||
|
"en": "Elara Musayelyan <elara@elara.ws>",
|
||||||
|
"ru": "Элара Мусаелян <elara@elara.ws>",
|
||||||
|
}),
|
||||||
Architectures: db.NewJSON([]string{"arm64", "amd64"}),
|
Architectures: db.NewJSON([]string{"arm64", "amd64"}),
|
||||||
Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
|
Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
|
||||||
Provides: db.NewJSON([]string{"test"}),
|
Provides: db.NewJSON([]string{"test"}),
|
||||||
@ -32,50 +58,40 @@ var testPkg = db.Package{
|
|||||||
Repository: "default",
|
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) {
|
func TestInit(t *testing.T) {
|
||||||
gdb, err := sqlx.Open("sqlite", ":memory:")
|
_, err := db.Open(":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
defer gdb.Close()
|
defer db.Close()
|
||||||
|
|
||||||
err = db.Init(gdb)
|
_, err = db.DB().Exec("SELECT * FROM pkgs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = gdb.Exec("SELECT * FROM pkgs")
|
ver, ok := db.GetVersion()
|
||||||
if err != nil {
|
if !ok {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
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) {
|
func TestInsertPackage(t *testing.T) {
|
||||||
gdb, err := getDB(t)
|
_, err := db.Open(":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
defer gdb.Close()
|
defer db.Close()
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, testPkg)
|
err = db.InsertPackage(testPkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dbPkg := db.Package{}
|
dbPkg := db.Package{}
|
||||||
err = sqlx.Get(gdb, &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
|
err = sqlx.Get(db.DB(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
@ -86,28 +102,28 @@ func TestInsertPackage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPkgs(t *testing.T) {
|
func TestGetPkgs(t *testing.T) {
|
||||||
gdb, err := getDB(t)
|
_, err := db.Open(":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
defer gdb.Close()
|
defer db.Close()
|
||||||
|
|
||||||
x1 := testPkg
|
x1 := testPkg
|
||||||
x1.Name = "x1"
|
x1.Name = "x1"
|
||||||
x2 := testPkg
|
x2 := testPkg
|
||||||
x2.Name = "x2"
|
x2.Name = "x2"
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, x1)
|
err = db.InsertPackage(x1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, x2)
|
err = db.InsertPackage(x2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := db.GetPkgs(gdb, "name LIKE 'x%'")
|
result, err := db.GetPkgs("name LIKE 'x%'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
@ -126,28 +142,28 @@ func TestGetPkgs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPkg(t *testing.T) {
|
func TestGetPkg(t *testing.T) {
|
||||||
gdb, err := getDB(t)
|
_, err := db.Open(":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
defer gdb.Close()
|
defer db.Close()
|
||||||
|
|
||||||
x1 := testPkg
|
x1 := testPkg
|
||||||
x1.Name = "x1"
|
x1.Name = "x1"
|
||||||
x2 := testPkg
|
x2 := testPkg
|
||||||
x2.Name = "x2"
|
x2.Name = "x2"
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, x1)
|
err = db.InsertPackage(x1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, x2)
|
err = db.InsertPackage(x2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg, err := db.GetPkg(gdb, "name LIKE 'x%' ORDER BY name")
|
pkg, err := db.GetPkg("name LIKE 'x%' ORDER BY name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
@ -162,34 +178,34 @@ func TestGetPkg(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDeletePkgs(t *testing.T) {
|
func TestDeletePkgs(t *testing.T) {
|
||||||
gdb, err := getDB(t)
|
_, err := db.Open(":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
defer gdb.Close()
|
defer db.Close()
|
||||||
|
|
||||||
x1 := testPkg
|
x1 := testPkg
|
||||||
x1.Name = "x1"
|
x1.Name = "x1"
|
||||||
x2 := testPkg
|
x2 := testPkg
|
||||||
x2.Name = "x2"
|
x2.Name = "x2"
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, x1)
|
err = db.InsertPackage(x1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, x2)
|
err = db.InsertPackage(x2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.DeletePkgs(gdb, "name = 'x1'")
|
err = db.DeletePkgs("name = 'x1'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dbPkg db.Package
|
var dbPkg db.Package
|
||||||
err = gdb.Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;")
|
err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
@ -200,11 +216,11 @@ func TestDeletePkgs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJsonArrayContains(t *testing.T) {
|
func TestJsonArrayContains(t *testing.T) {
|
||||||
gdb, err := getDB(t)
|
_, err := db.Open(":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
defer gdb.Close()
|
defer db.Close()
|
||||||
|
|
||||||
x1 := testPkg
|
x1 := testPkg
|
||||||
x1.Name = "x1"
|
x1.Name = "x1"
|
||||||
@ -212,18 +228,18 @@ func TestJsonArrayContains(t *testing.T) {
|
|||||||
x2.Name = "x2"
|
x2.Name = "x2"
|
||||||
x2.Provides.Val = append(x2.Provides.Val, "x")
|
x2.Provides.Val = append(x2.Provides.Val, "x")
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, x1)
|
err = db.InsertPackage(x1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, x2)
|
err = db.InsertPackage(x2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dbPkg db.Package
|
var dbPkg db.Package
|
||||||
err = gdb.Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
|
err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
379
internal/dl/dl.go
Normal file
379
internal/dl/dl.go
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/purell"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
|
"golang.org/x/crypto/blake2s"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
"lure.sh/lure/internal/dlcache"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
ErrNoSuchHashAlgo = errors.New("dl: invalid hashing algorithm")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Downloaders contains all the downloaders in the order in which
|
||||||
|
// they should be checked
|
||||||
|
var Downloaders = []Downloader{
|
||||||
|
GitDownloader{},
|
||||||
|
TorrentDownloader{},
|
||||||
|
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 {
|
||||||
|
Hash []byte
|
||||||
|
HashAlgorithm string
|
||||||
|
Name string
|
||||||
|
URL string
|
||||||
|
Destination string
|
||||||
|
CacheDisabled bool
|
||||||
|
PostprocDisabled bool
|
||||||
|
Progress io.Writer
|
||||||
|
LocalDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts Options) NewHash() (hash.Hash, error) {
|
||||||
|
switch opts.HashAlgorithm {
|
||||||
|
case "", "sha256":
|
||||||
|
return sha256.New(), nil
|
||||||
|
case "sha224":
|
||||||
|
return sha256.New224(), nil
|
||||||
|
case "sha512":
|
||||||
|
return sha512.New(), nil
|
||||||
|
case "sha384":
|
||||||
|
return sha512.New384(), nil
|
||||||
|
case "sha1":
|
||||||
|
return sha1.New(), nil
|
||||||
|
case "md5":
|
||||||
|
return md5.New(), nil
|
||||||
|
case "blake2s-128":
|
||||||
|
return blake2s.New256(nil)
|
||||||
|
case "blake2s-256":
|
||||||
|
return blake2s.New256(nil)
|
||||||
|
case "blake2b-256":
|
||||||
|
return blake2b.New(32, nil)
|
||||||
|
case "blake2b-512":
|
||||||
|
return blake2b.New(64, nil)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrNoSuchHashAlgo, opts.HashAlgorithm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
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(ctx, 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{
|
||||||
|
Hash: opts.Hash,
|
||||||
|
HashAlgorithm: opts.HashAlgorithm,
|
||||||
|
Name: opts.Name,
|
||||||
|
URL: opts.URL,
|
||||||
|
Destination: cacheDir,
|
||||||
|
Progress: opts.Progress,
|
||||||
|
LocalDir: opts.LocalDir,
|
||||||
|
})
|
||||||
|
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, m.Name, t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok && !updated {
|
||||||
|
log.Info("Source found in cache and linked to destination").Str("source", opts.Name).Stringer("type", t).Send()
|
||||||
|
return nil
|
||||||
|
} else if ok {
|
||||||
|
log.Info("Source updated and linked to destination").Str("source", opts.Name).Stringer("type", t).Send()
|
||||||
|
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(ctx, opts.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, name, err := d.Download(Options{
|
||||||
|
Hash: opts.Hash,
|
||||||
|
HashAlgorithm: opts.HashAlgorithm,
|
||||||
|
Name: opts.Name,
|
||||||
|
URL: opts.URL,
|
||||||
|
Destination: cacheDir,
|
||||||
|
Progress: opts.Progress,
|
||||||
|
LocalDir: opts.LocalDir,
|
||||||
|
})
|
||||||
|
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, name, 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, name string, t Type) (bool, error) {
|
||||||
|
switch t {
|
||||||
|
case TypeFile:
|
||||||
|
cd, err := os.Open(cacheDir)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := cd.Readdirnames(0)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cd.Close()
|
||||||
|
|
||||||
|
if slices.Contains(names, name) {
|
||||||
|
err = os.Link(filepath.Join(cacheDir, name), dest)
|
||||||
|
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
|
||||||
|
|
||||||
|
u, err := purell.NormalizeURLString(u, normalizationFlags)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix magnet URLs after normalization
|
||||||
|
u = strings.Replace(u, "magnet://", "magnet:", 1)
|
||||||
|
return u, nil
|
||||||
|
}
|
264
internal/dl/file.go
Normal file
264
internal/dl/file.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mholt/archiver/v4"
|
||||||
|
"github.com/schollz/progressbar/v3"
|
||||||
|
"lure.sh/lure/internal/shutils/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
var r io.ReadCloser
|
||||||
|
var size int64
|
||||||
|
if u.Scheme == "local" {
|
||||||
|
localFl, err := os.Open(filepath.Join(opts.LocalDir, u.Path))
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
fi, err := localFl.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
size = fi.Size()
|
||||||
|
if name == "" {
|
||||||
|
name = fi.Name()
|
||||||
|
}
|
||||||
|
r = localFl
|
||||||
|
} else {
|
||||||
|
res, err := http.Get(u.String())
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
size = res.ContentLength
|
||||||
|
if name == "" {
|
||||||
|
name = getFilename(res)
|
||||||
|
}
|
||||||
|
r = res.Body
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
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(
|
||||||
|
size,
|
||||||
|
progressbar.OptionSetDescription(name),
|
||||||
|
progressbar.OptionSetWriter(opts.Progress),
|
||||||
|
progressbar.OptionShowBytes(true),
|
||||||
|
progressbar.OptionSetWidth(10),
|
||||||
|
progressbar.OptionThrottle(65*time.Millisecond),
|
||||||
|
progressbar.OptionShowCount(),
|
||||||
|
progressbar.OptionOnCompletion(func() {
|
||||||
|
_, _ = io.WriteString(opts.Progress, "\n")
|
||||||
|
}),
|
||||||
|
progressbar.OptionSpinnerType(14),
|
||||||
|
progressbar.OptionFullWidth(),
|
||||||
|
progressbar.OptionSetRenderBlankState(true),
|
||||||
|
)
|
||||||
|
defer bar.Close()
|
||||||
|
} else {
|
||||||
|
bar = handlers.NopRWC{}
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := opts.NewHash()
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var w io.Writer
|
||||||
|
if opts.Hash != nil {
|
||||||
|
w = io.MultiWriter(fl, h, bar)
|
||||||
|
} else {
|
||||||
|
w = io.MultiWriter(fl, bar)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(w, r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
r.Close()
|
||||||
|
|
||||||
|
if opts.Hash != nil {
|
||||||
|
sum := h.Sum(nil)
|
||||||
|
if !bytes.Equal(sum, opts.Hash) {
|
||||||
|
return 0, "", ErrChecksumMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.PostprocDisabled {
|
||||||
|
return TypeFile, name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fl.Seek(0, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
format, ar, err := archiver.Identify(name, fl)
|
||||||
|
if err == archiver.ErrNoMatch {
|
||||||
|
return TypeFile, name, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = extractFile(ar, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
_, params, err := mime.ParseMediaType(res.Header.Get("Content-Disposition"))
|
||||||
|
if err != nil {
|
||||||
|
return path.Base(res.Request.URL.Path)
|
||||||
|
}
|
||||||
|
if filename, ok := params["filename"]; ok {
|
||||||
|
return filename
|
||||||
|
} else {
|
||||||
|
return path.Base(res.Request.URL.Path)
|
||||||
|
}
|
||||||
|
}
|
198
internal/dl/git.go
Normal file
198
internal/dl/git.go
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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/config"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.Fetch(&git.FetchOptions{
|
||||||
|
RefSpecs: []config.RefSpec{"+refs/*:refs/*"},
|
||||||
|
})
|
||||||
|
if err != git.NoErrAlreadyUpToDate && 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
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := getManifest(opts.Destination)
|
||||||
|
manifestOK := err == nil
|
||||||
|
|
||||||
|
err = w.Pull(po)
|
||||||
|
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||||
|
return false, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifestOK {
|
||||||
|
err = writeManifest(opts.Destination, m)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
90
internal/dl/torrent.go
Normal file
90
internal/dl/torrent.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package dl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
urlMatchRegex = regexp.MustCompile(`(magnet|torrent\+https?):.*`)
|
||||||
|
ErrAria2NotFound = errors.New("aria2 must be installed for torrent functionality")
|
||||||
|
ErrDestinationEmpty = errors.New("the destination directory is empty")
|
||||||
|
)
|
||||||
|
|
||||||
|
type TorrentDownloader struct{}
|
||||||
|
|
||||||
|
// Name always returns "file"
|
||||||
|
func (TorrentDownloader) Name() string {
|
||||||
|
return "torrent"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchURL returns true if the URL is a magnet link
|
||||||
|
// or an http(s) link with a "torrent+" prefix
|
||||||
|
func (TorrentDownloader) MatchURL(u string) bool {
|
||||||
|
return urlMatchRegex.MatchString(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download downloads a file over the BitTorrent protocol.
|
||||||
|
func (TorrentDownloader) Download(opts Options) (Type, string, error) {
|
||||||
|
aria2Path, err := exec.LookPath("aria2c")
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", ErrAria2NotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.URL = strings.TrimPrefix(opts.URL, "torrent+")
|
||||||
|
|
||||||
|
cmd := exec.Command(aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("aria2c returned an error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = removeTorrentFiles(opts.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return determineType(opts.Destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTorrentFiles(path string) error {
|
||||||
|
filePaths, err := filepath.Glob(filepath.Join(path, "*.torrent"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, filePath := range filePaths {
|
||||||
|
err = os.Remove(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func determineType(path string) (Type, string, error) {
|
||||||
|
files, err := os.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(files) > 1 {
|
||||||
|
return TypeDir, "", nil
|
||||||
|
} else if len(files) == 1 {
|
||||||
|
if files[0].IsDir() {
|
||||||
|
return TypeDir, files[0].Name(), nil
|
||||||
|
} else {
|
||||||
|
return TypeFile, files[0].Name(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, "", ErrDestinationEmpty
|
||||||
|
}
|
93
internal/dlcache/dlcache.go
Normal file
93
internal/dlcache/dlcache.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 (
|
||||||
|
"context"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"lure.sh/lure/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BasePath returns the base path of the download cache
|
||||||
|
func BasePath(ctx context.Context) string {
|
||||||
|
return filepath.Join(config.GetPaths(ctx).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(ctx context.Context, id string) (string, error) {
|
||||||
|
h, err := hashID(id)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
itemPath := filepath.Join(BasePath(ctx), 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(ctx context.Context, id string) (string, bool) {
|
||||||
|
h, err := hashID(id)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
itemPath := filepath.Join(BasePath(ctx), 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
|
||||||
|
}
|
76
internal/dlcache/dlcache_test.go
Normal file
76
internal/dlcache/dlcache_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 (
|
||||||
|
"context"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"lure.sh/lure/internal/config"
|
||||||
|
"lure.sh/lure/internal/dlcache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dir, err := os.MkdirTemp("/tmp", "lure-dlcache-test.*")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
config.GetPaths(context.Background()).RepoDir = 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))
|
||||||
|
}
|
110
internal/osutils/move.go
Normal file
110
internal/osutils/move.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 osutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Move attempts to use os.Rename and if that fails (such as for a cross-device move),
|
||||||
|
// it instead copies the source to the destination and then removes the source.
|
||||||
|
func Move(sourcePath, destPath string) error {
|
||||||
|
// Try to rename the source to the destination
|
||||||
|
err := os.Rename(sourcePath, destPath)
|
||||||
|
if err == nil {
|
||||||
|
return nil // Successful move
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename failed, so copy the source to the destination
|
||||||
|
err = copyDirOrFile(sourcePath, destPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy successful, remove the original source
|
||||||
|
err = os.RemoveAll(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDirOrFile(sourcePath, destPath string) error {
|
||||||
|
sourceInfo, err := os.Stat(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceInfo.IsDir() {
|
||||||
|
return copyDir(sourcePath, destPath, sourceInfo)
|
||||||
|
} else if sourceInfo.Mode().IsRegular() {
|
||||||
|
return copyFile(sourcePath, destPath, sourceInfo)
|
||||||
|
} else {
|
||||||
|
// ignore non-regular files
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDir(sourcePath, destPath string, sourceInfo os.FileInfo) error {
|
||||||
|
err := os.MkdirAll(destPath, sourceInfo.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
sourceEntry := filepath.Join(sourcePath, entry.Name())
|
||||||
|
destEntry := filepath.Join(destPath, entry.Name())
|
||||||
|
|
||||||
|
err = copyDirOrFile(sourceEntry, destEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(sourcePath, destPath string, sourceInfo os.FileInfo) error {
|
||||||
|
sourceFile, err := os.Open(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sourceFile.Close()
|
||||||
|
|
||||||
|
destFile, err := os.OpenFile(destPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, sourceInfo.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destFile, sourceFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,40 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package overrides
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.arsenm.dev/lure/distro"
|
"lure.sh/lure/internal/cpu"
|
||||||
"go.arsenm.dev/lure/internal/cpu"
|
"lure.sh/lure/internal/db"
|
||||||
|
"lure.sh/lure/pkg/distro"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Opts struct {
|
type Opts struct {
|
||||||
Name string
|
Name string
|
||||||
Overrides bool
|
Overrides bool
|
||||||
LikeDistros bool
|
LikeDistros bool
|
||||||
|
Languages []string
|
||||||
|
LanguageTags []language.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultOpts = &Opts{
|
var DefaultOpts = &Opts{
|
||||||
Overrides: true,
|
Overrides: true,
|
||||||
LikeDistros: true,
|
LikeDistros: true,
|
||||||
|
Languages: []string{"en"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve generates a slice of possible override names in the order that they should be checked
|
// 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 {
|
if opts == nil {
|
||||||
opts = DefaultOpts
|
opts = DefaultOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.Overrides {
|
if !opts.Overrides {
|
||||||
return []string{opts.Name}
|
return []string{opts.Name}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
architectures := []string{runtime.GOARCH}
|
langs, err := parseLangs(opts.Languages, opts.LanguageTags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if runtime.GOARCH == "arm" {
|
architectures, err := cpu.CompatibleArches(cpu.Arch())
|
||||||
// More specific goes first
|
if err != nil {
|
||||||
architectures[0] = cpu.ARMVariant()
|
return nil, err
|
||||||
architectures = append(architectures, "arm")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
distros := []string{info.ID}
|
distros := []string{info.ID}
|
||||||
@ -43,35 +69,41 @@ func Resolve(info *distro.OSRelease, opts *Opts) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var out []string
|
var out []string
|
||||||
for _, arch := range architectures {
|
for _, lang := range langs {
|
||||||
for _, distro := range distros {
|
for _, distro := range distros {
|
||||||
if opts.Name == "" {
|
for _, arch := range architectures {
|
||||||
out = append(
|
out = append(out, opts.Name+"_"+arch+"_"+distro+"_"+lang)
|
||||||
out,
|
|
||||||
arch+"_"+distro,
|
|
||||||
distro,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
out = append(
|
|
||||||
out,
|
|
||||||
opts.Name+"_"+arch+"_"+distro,
|
|
||||||
opts.Name+"_"+distro,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out = append(out, opts.Name+"_"+distro+"_"+lang)
|
||||||
}
|
}
|
||||||
if opts.Name == "" {
|
|
||||||
out = append(out, arch)
|
for _, arch := range architectures {
|
||||||
} else {
|
out = append(out, opts.Name+"_"+arch+"_"+lang)
|
||||||
out = append(out, opts.Name+"_"+arch)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out = append(out, opts.Name+"_"+lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, distro := range distros {
|
||||||
|
for _, arch := range architectures {
|
||||||
|
out = append(out, opts.Name+"_"+arch+"_"+distro)
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, opts.Name+"_"+distro)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arch := range architectures {
|
||||||
|
out = append(out, opts.Name+"_"+arch)
|
||||||
|
}
|
||||||
|
|
||||||
out = append(out, opts.Name)
|
out = append(out, opts.Name)
|
||||||
|
|
||||||
for index, item := range out {
|
for index, item := range out {
|
||||||
out[index] = strings.ReplaceAll(item, "-", "_")
|
out[index] = strings.TrimPrefix(strings.ReplaceAll(item, "-", "_"), "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Opts) WithName(name string) *Opts {
|
func (o *Opts) WithName(name string) *Opts {
|
||||||
@ -97,3 +129,95 @@ func (o *Opts) WithLikeDistros(v bool) *Opts {
|
|||||||
out.LikeDistros = v
|
out.LikeDistros = v
|
||||||
return out
|
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"`
|
||||||
|
OptDepends []string `sh:"opt_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,11 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package overrides_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.arsenm.dev/lure/distro"
|
"lure.sh/lure/internal/overrides"
|
||||||
"go.arsenm.dev/lure/internal/overrides"
|
"lure.sh/lure/pkg/distro"
|
||||||
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
var info = &distro.OSRelease{
|
var info = &distro.OSRelease{
|
||||||
@ -14,9 +34,20 @@ var info = &distro.OSRelease{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolve(t *testing.T) {
|
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{
|
expected := []string{
|
||||||
|
"amd64_centos_en",
|
||||||
|
"centos_en",
|
||||||
|
"amd64_rhel_en",
|
||||||
|
"rhel_en",
|
||||||
|
"amd64_fedora_en",
|
||||||
|
"fedora_en",
|
||||||
|
"amd64_en",
|
||||||
|
"en",
|
||||||
"amd64_centos",
|
"amd64_centos",
|
||||||
"centos",
|
"centos",
|
||||||
"amd64_rhel",
|
"amd64_rhel",
|
||||||
@ -33,11 +64,14 @@ func TestResolve(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveName(t *testing.T) {
|
func TestResolveName(t *testing.T) {
|
||||||
names := overrides.Resolve(info, &overrides.Opts{
|
names, err := overrides.Resolve(info, &overrides.Opts{
|
||||||
Name: "deps",
|
Name: "deps",
|
||||||
Overrides: true,
|
Overrides: true,
|
||||||
LikeDistros: true,
|
LikeDistros: true,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
expected := []string{
|
expected := []string{
|
||||||
"deps_amd64_centos",
|
"deps_amd64_centos",
|
||||||
@ -55,11 +89,51 @@ func TestResolveName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveArch(t *testing.T) {
|
||||||
|
os.Setenv("LURE_ARCH", "arm7")
|
||||||
|
defer os.Setenv("LURE_ARCH", "")
|
||||||
|
|
||||||
|
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_arm7_centos",
|
||||||
|
"deps_arm6_centos",
|
||||||
|
"deps_arm5_centos",
|
||||||
|
"deps_centos",
|
||||||
|
"deps_arm7_rhel",
|
||||||
|
"deps_arm6_rhel",
|
||||||
|
"deps_arm5_rhel",
|
||||||
|
"deps_rhel",
|
||||||
|
"deps_arm7_fedora",
|
||||||
|
"deps_arm6_fedora",
|
||||||
|
"deps_arm5_fedora",
|
||||||
|
"deps_fedora",
|
||||||
|
"deps_arm7",
|
||||||
|
"deps_arm6",
|
||||||
|
"deps_arm5",
|
||||||
|
"deps",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(names, expected) {
|
||||||
|
t.Errorf("expected %v, got %v", expected, names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolveNoLikeDistros(t *testing.T) {
|
func TestResolveNoLikeDistros(t *testing.T) {
|
||||||
names := overrides.Resolve(info, &overrides.Opts{
|
names, err := overrides.Resolve(info, &overrides.Opts{
|
||||||
Overrides: true,
|
Overrides: true,
|
||||||
LikeDistros: false,
|
LikeDistros: false,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
expected := []string{
|
expected := []string{
|
||||||
"amd64_centos",
|
"amd64_centos",
|
||||||
@ -74,11 +148,14 @@ func TestResolveNoLikeDistros(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveNoOverrides(t *testing.T) {
|
func TestResolveNoOverrides(t *testing.T) {
|
||||||
names := overrides.Resolve(info, &overrides.Opts{
|
names, err := overrides.Resolve(info, &overrides.Opts{
|
||||||
Name: "deps",
|
Name: "deps",
|
||||||
Overrides: false,
|
Overrides: false,
|
||||||
LikeDistros: false,
|
LikeDistros: false,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
expected := []string{"deps"}
|
expected := []string{"deps"}
|
||||||
|
|
||||||
@ -86,3 +163,33 @@ func TestResolveNoOverrides(t *testing.T) {
|
|||||||
t.Errorf("expected %v, got %v", expected, names)
|
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",
|
||||||
|
"en",
|
||||||
|
"amd64_centos_ru",
|
||||||
|
"centos_ru",
|
||||||
|
"amd64_ru",
|
||||||
|
"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 Elara 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
|
package pager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package pager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -16,13 +34,13 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
b := lipgloss.RoundedBorder()
|
b1 := lipgloss.RoundedBorder()
|
||||||
b.Right = "\u251C"
|
b1.Right = "\u251C"
|
||||||
titleStyle = lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
|
titleStyle = lipgloss.NewStyle().BorderStyle(b1).Padding(0, 1)
|
||||||
|
|
||||||
b = lipgloss.RoundedBorder()
|
b2 := lipgloss.RoundedBorder()
|
||||||
b.Left = "\u2524"
|
b2.Left = "\u2524"
|
||||||
infoStyle = titleStyle.Copy().BorderStyle(b)
|
infoStyle = titleStyle.Copy().BorderStyle(b2)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pager struct {
|
type Pager struct {
|
||||||
@ -42,6 +60,7 @@ func (p *Pager) Run() error {
|
|||||||
prog := tea.NewProgram(
|
prog := tea.NewProgram(
|
||||||
p.model,
|
p.model,
|
||||||
tea.WithMouseCellMotion(),
|
tea.WithMouseCellMotion(),
|
||||||
|
tea.WithAltScreen(),
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err := prog.Run()
|
_, err := prog.Run()
|
||||||
@ -56,7 +75,7 @@ type pagerModel struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pm pagerModel) Init() tea.Cmd {
|
func (pm pagerModel) Init() tea.Cmd {
|
||||||
return tea.ClearScreen
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm pagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (pm pagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
package repos
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.arsenm.dev/lure/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FindPkgs looks for packages matching the inputs inside the database.
|
|
||||||
// It returns a map that maps the package name input to the packages found for it.
|
|
||||||
// It also returns a slice that contains the names of all packages that were not found.
|
|
||||||
func FindPkgs(gdb *sqlx.DB, pkgs []string) (map[string][]db.Package, []string, error) {
|
|
||||||
found := map[string][]db.Package{}
|
|
||||||
notFound := []string(nil)
|
|
||||||
|
|
||||||
for _, pkgName := range pkgs {
|
|
||||||
if pkgName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.GetPkgs(gdb, "name LIKE ?", pkgName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
added := 0
|
|
||||||
for result.Next() {
|
|
||||||
var pkg db.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
added++
|
|
||||||
found[pkgName] = append(found[pkgName], pkg)
|
|
||||||
}
|
|
||||||
result.Close()
|
|
||||||
|
|
||||||
if added == 0 {
|
|
||||||
result, err := db.GetPkgs(gdb, "json_array_contains(provides, ?)", pkgName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for result.Next() {
|
|
||||||
var pkg db.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
added++
|
|
||||||
found[pkgName] = append(found[pkgName], pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if added == 0 {
|
|
||||||
notFound = append(notFound, pkgName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found, notFound, nil
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package repos_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"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"
|
|
||||||
"go.arsenm.dev/lure/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setCfgDirs(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
config.CacheDir, err = os.MkdirTemp("/tmp", "lure-pull-test.*")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.RepoDir = filepath.Join(config.CacheDir, "repo")
|
|
||||||
config.PkgsDir = filepath.Join(config.CacheDir, "pkgs")
|
|
||||||
|
|
||||||
err = os.MkdirAll(config.RepoDir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(config.PkgsDir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.DBPath = filepath.Join(config.CacheDir, "db")
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeCacheDir(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
err := os.RemoveAll(config.CacheDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPull(t *testing.T) {
|
|
||||||
gdb, err := sqlx.Open("sqlite", ":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)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
err = repos.Pull(ctx, gdb, []types.Repo{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
URL: "https://github.com/Arsen6331/lure-repo.git",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.GetPkgs(gdb, "name LIKE 'itd%'")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkgAmt int
|
|
||||||
for result.Next() {
|
|
||||||
var dbPkg db.Package
|
|
||||||
err = result.StructScan(&dbPkg)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
pkgAmt++
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkgAmt < 2 {
|
|
||||||
t.Errorf("Expected 2 packages to match, got %d", pkgAmt)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -25,8 +25,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"go.arsenm.dev/lure/distro"
|
"lure.sh/lure/internal/overrides"
|
||||||
"go.arsenm.dev/lure/internal/overrides"
|
"lure.sh/lure/pkg/distro"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"mvdan.cc/sh/v3/expand"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
@ -56,16 +56,16 @@ func (ite InvalidTypeError) Error() string {
|
|||||||
// Decoder provides methods for decoding variable values
|
// Decoder provides methods for decoding variable values
|
||||||
type Decoder struct {
|
type Decoder struct {
|
||||||
info *distro.OSRelease
|
info *distro.OSRelease
|
||||||
runner *interp.Runner
|
Runner *interp.Runner
|
||||||
// Enable distro overrides (true by default)
|
// Enable distro overrides (true by default)
|
||||||
Overrides bool
|
Overrides bool
|
||||||
// Enable using like distros for overrides (true by default)
|
// Enable using like distros for overrides
|
||||||
LikeDistros bool
|
LikeDistros bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new variable decoder
|
// New creates a new variable decoder
|
||||||
func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
||||||
return &Decoder{info, runner, true, true}
|
return &Decoder{info, runner, true, len(info.Like) > 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeVar decodes a variable to val using reflection.
|
// DecodeVar decodes a variable to val using reflection.
|
||||||
@ -173,7 +173,7 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return func(ctx context.Context, opts ...interp.RunnerOption) error {
|
return func(ctx context.Context, opts ...interp.RunnerOption) error {
|
||||||
sub := d.runner.Subshell()
|
sub := d.Runner.Subshell()
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(sub)
|
opt(sub)
|
||||||
}
|
}
|
||||||
@ -182,9 +182,13 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
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 {
|
for _, fnName := range names {
|
||||||
fn, ok := d.runner.Funcs[fnName]
|
fn, ok := d.Runner.Funcs[fnName]
|
||||||
if ok {
|
if ok {
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
@ -195,13 +199,17 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
|||||||
// getVar gets a variable based on its name, taking into account
|
// getVar gets a variable based on its name, taking into account
|
||||||
// override variables and nameref variables.
|
// override variables and nameref variables.
|
||||||
func (d *Decoder) getVar(name string) *expand.Variable {
|
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 {
|
for _, varName := range names {
|
||||||
val, ok := d.runner.Vars[varName]
|
val, ok := d.Runner.Vars[varName]
|
||||||
if ok {
|
if ok {
|
||||||
// Resolve nameref variables
|
// Resolve nameref variables
|
||||||
_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
|
_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
|
||||||
if val, ok := d.runner.Vars[s]; ok {
|
if val, ok := d.Runner.Vars[s]; ok {
|
||||||
return val.String()
|
return val.String()
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package decoder_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -9,8 +27,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.arsenm.dev/lure/distro"
|
"lure.sh/lure/internal/shutils/decoder"
|
||||||
"go.arsenm.dev/lure/internal/shutils/decoder"
|
"lure.sh/lure/pkg/distro"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package shutils
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,13 +1,31 @@
|
|||||||
package shutils_test
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 handlers_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.arsenm.dev/lure/distro"
|
"lure.sh/lure/internal/shutils/handlers"
|
||||||
"go.arsenm.dev/lure/internal/shutils"
|
"lure.sh/lure/internal/shutils/decoder"
|
||||||
"go.arsenm.dev/lure/internal/shutils/decoder"
|
"lure.sh/lure/pkg/distro"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
@ -18,8 +36,8 @@ const testScript = `
|
|||||||
release=1
|
release=1
|
||||||
epoch=2
|
epoch=2
|
||||||
desc="Test package"
|
desc="Test package"
|
||||||
homepage='https://lure.arsenm.dev'
|
homepage='https://lure.sh'
|
||||||
maintainer='Arsen Musayelyan <arsen@arsenm.dev>'
|
maintainer='Elara Musayelyan <elara@elara.ws>'
|
||||||
architectures=('arm64' 'amd64')
|
architectures=('arm64' 'amd64')
|
||||||
license=('GPL-3.0-or-later')
|
license=('GPL-3.0-or-later')
|
||||||
provides=('test')
|
provides=('test')
|
114
internal/shutils/handlers/fakeroot.go
Normal file
114
internal/shutils/handlers/fakeroot.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"lure.sh/fakeroot"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go
|
||||||
|
// and modified to run commands in a fakeroot environent.
|
||||||
|
func FakerootExecHandler(killTimeout time.Duration) interp.ExecHandlerFunc {
|
||||||
|
return func(ctx context.Context, args []string) error {
|
||||||
|
hc := interp.HandlerCtx(ctx)
|
||||||
|
path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(hc.Stderr, err)
|
||||||
|
return interp.NewExitStatus(127)
|
||||||
|
}
|
||||||
|
cmd := &exec.Cmd{
|
||||||
|
Path: path,
|
||||||
|
Args: args,
|
||||||
|
Env: execEnv(hc.Env),
|
||||||
|
Dir: hc.Dir,
|
||||||
|
Stdin: hc.Stdin,
|
||||||
|
Stdout: hc.Stdout,
|
||||||
|
Stderr: hc.Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fakeroot.Apply(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err == nil {
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
go func() {
|
||||||
|
<-done
|
||||||
|
|
||||||
|
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
||||||
|
_ = cmd.Process.Signal(os.Kill)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: don't temporarily leak this goroutine
|
||||||
|
// if the program stops itself with the
|
||||||
|
// interrupt.
|
||||||
|
go func() {
|
||||||
|
time.Sleep(killTimeout)
|
||||||
|
_ = cmd.Process.Signal(os.Kill)
|
||||||
|
}()
|
||||||
|
_ = cmd.Process.Signal(os.Interrupt)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x := err.(type) {
|
||||||
|
case *exec.ExitError:
|
||||||
|
// started, but errored - default to 1 if OS
|
||||||
|
// doesn't have exit statuses
|
||||||
|
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
||||||
|
if status.Signaled() {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
return interp.NewExitStatus(uint8(128 + status.Signal()))
|
||||||
|
}
|
||||||
|
return interp.NewExitStatus(uint8(status.ExitStatus()))
|
||||||
|
}
|
||||||
|
return interp.NewExitStatus(1)
|
||||||
|
case *exec.Error:
|
||||||
|
// did not start
|
||||||
|
fmt.Fprintf(hc.Stderr, "%v\n", err)
|
||||||
|
return interp.NewExitStatus(127)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execEnv was extracted from github.com/mvdan/sh/interp/vars.go
|
||||||
|
func execEnv(env expand.Environ) []string {
|
||||||
|
list := make([]string, 0, 64)
|
||||||
|
env.Each(func(name string, vr expand.Variable) bool {
|
||||||
|
if !vr.IsSet() {
|
||||||
|
// If a variable is set globally but unset in the
|
||||||
|
// runner, we need to ensure it's not part of the final
|
||||||
|
// list. Seems like zeroing the element is enough.
|
||||||
|
// This is a linear search, but this scenario should be
|
||||||
|
// rare, and the number of variables shouldn't be large.
|
||||||
|
for i, kv := range list {
|
||||||
|
if strings.HasPrefix(kv, name+"=") {
|
||||||
|
list[i] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vr.Exported && vr.Kind == expand.String {
|
||||||
|
list = append(list, name+"="+vr.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package shutils
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -46,8 +46,8 @@ func (NopRWC) Read([]byte) (int, error) {
|
|||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (NopRWC) Write([]byte) (int, error) {
|
func (NopRWC) Write(b []byte) (int, error) {
|
||||||
return 0, io.EOF
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (NopRWC) Close() error {
|
func (NopRWC) Close() error {
|
58
internal/shutils/handlers/nop_test.go
Normal file
58
internal/shutils/handlers/nop_test.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 handlers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"lure.sh/lure/internal/shutils/handlers"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNopExec(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
fl, err := syntax.NewParser().Parse(strings.NewReader(`/bin/echo test`), "lure.sh")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
runner, err := interp.New(
|
||||||
|
interp.ExecHandler(handlers.NopExec),
|
||||||
|
interp.StdIO(os.Stdin, buf, buf),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runner.Run(ctx, fl)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.String() != "" {
|
||||||
|
t.Fatalf("Expected empty string, got %#v", buf.String())
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,12 +16,13 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package shutils
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -30,33 +31,36 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc {
|
func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc {
|
||||||
return func(ctx context.Context, s string) ([]os.FileInfo, error) {
|
return func(ctx context.Context, s string) ([]fs.FileInfo, error) {
|
||||||
|
path := filepath.Clean(s)
|
||||||
for _, allowedPrefix := range allowedPrefixes {
|
for _, allowedPrefix := range allowedPrefixes {
|
||||||
if strings.HasPrefix(s, allowedPrefix) {
|
if strings.HasPrefix(path, allowedPrefix) {
|
||||||
return interp.DefaultReadDirHandler()(ctx, s)
|
return interp.DefaultReadDirHandler()(ctx, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, os.ErrNotExist
|
return nil, fs.ErrNotExist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RestrictedStat(allowedPrefixes ...string) interp.StatHandlerFunc {
|
func RestrictedStat(allowedPrefixes ...string) interp.StatHandlerFunc {
|
||||||
return func(ctx context.Context, s string, b bool) (os.FileInfo, error) {
|
return func(ctx context.Context, s string, b bool) (fs.FileInfo, error) {
|
||||||
|
path := filepath.Clean(s)
|
||||||
for _, allowedPrefix := range allowedPrefixes {
|
for _, allowedPrefix := range allowedPrefixes {
|
||||||
if strings.HasPrefix(s, allowedPrefix) {
|
if strings.HasPrefix(path, allowedPrefix) {
|
||||||
return interp.DefaultStatHandler()(ctx, s, b)
|
return interp.DefaultStatHandler()(ctx, s, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, os.ErrNotExist
|
return nil, fs.ErrNotExist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RestrictedOpen(allowedPrefixes ...string) interp.OpenHandlerFunc {
|
func RestrictedOpen(allowedPrefixes ...string) interp.OpenHandlerFunc {
|
||||||
return func(ctx context.Context, s string, i int, fm os.FileMode) (io.ReadWriteCloser, error) {
|
return func(ctx context.Context, s string, i int, fm fs.FileMode) (io.ReadWriteCloser, error) {
|
||||||
|
path := filepath.Clean(s)
|
||||||
for _, allowedPrefix := range allowedPrefixes {
|
for _, allowedPrefix := range allowedPrefixes {
|
||||||
if strings.HasPrefix(s, allowedPrefix) {
|
if strings.HasPrefix(path, allowedPrefix) {
|
||||||
return interp.DefaultOpenHandler()(ctx, s, i, fm)
|
return interp.DefaultOpenHandler()(ctx, s, i, fm)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,22 @@
|
|||||||
package main
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -12,8 +30,8 @@ import (
|
|||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
"go.arsenm.dev/lure/internal/shutils"
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
"lure.sh/lure/internal/shutils/handlers"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,28 +40,31 @@ var (
|
|||||||
ErrNoDetectManNum = errors.New("manual number cannot be detected from the filename")
|
ErrNoDetectManNum = errors.New("manual number cannot be detected from the filename")
|
||||||
)
|
)
|
||||||
|
|
||||||
var helpers = shutils.ExecFuncs{
|
// Helpers contains all the helper commands
|
||||||
|
var Helpers = handlers.ExecFuncs{
|
||||||
"install-binary": installHelperCmd("/usr/bin", 0o755),
|
"install-binary": installHelperCmd("/usr/bin", 0o755),
|
||||||
"install-systemd-user": installHelperCmd("/usr/lib/systemd/user", 0o644),
|
"install-systemd-user": installHelperCmd("/usr/lib/systemd/user", 0o644),
|
||||||
"install-systemd": installHelperCmd("/usr/lib/systemd/system", 0o644),
|
"install-systemd": installHelperCmd("/usr/lib/systemd/system", 0o644),
|
||||||
"install-config": installHelperCmd("/etc", 0o644),
|
"install-config": installHelperCmd("/etc", 0o644),
|
||||||
"install-license": installHelperCmd("/usr/share/licenses", 0o644),
|
"install-license": installHelperCmd("/usr/share/licenses", 0o644),
|
||||||
"install-desktop": installHelperCmd("/usr/share/applications", 0o644),
|
"install-desktop": installHelperCmd("/usr/share/applications", 0o644),
|
||||||
|
"install-icon": installHelperCmd("/usr/share/pixmaps", 0o644),
|
||||||
"install-manual": installManualCmd,
|
"install-manual": installManualCmd,
|
||||||
"install-completion": installCompletionCmd,
|
"install-completion": installCompletionCmd,
|
||||||
"install-library": installLibraryCmd,
|
"install-library": installLibraryCmd,
|
||||||
"git-version": gitVersionCmd,
|
"git-version": gitVersionCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
// rHelpers contains restricted read-only helpers that don't modify any state
|
// Restricted contains restricted read-only helper commands
|
||||||
var rHelpers = shutils.ExecFuncs{
|
// that don't modify any state
|
||||||
|
var Restricted = handlers.ExecFuncs{
|
||||||
"git-version": gitVersionCmd,
|
"git-version": gitVersionCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
func installHelperCmd(prefix string, perms os.FileMode) shutils.ExecFunc {
|
func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc {
|
||||||
return func(hc interp.HandlerContext, cmd string, args []string) error {
|
return func(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return shutils.InsufficientArgsError(cmd, 1, len(args))
|
return handlers.InsufficientArgsError(cmd, 1, len(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
from := resolvePath(hc, args[0])
|
from := resolvePath(hc, args[0])
|
||||||
@ -64,7 +85,7 @@ func installHelperCmd(prefix string, perms os.FileMode) shutils.ExecFunc {
|
|||||||
|
|
||||||
func installManualCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
func installManualCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return shutils.InsufficientArgsError(cmd, 1, len(args))
|
return handlers.InsufficientArgsError(cmd, 1, len(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
from := resolvePath(hc, args[0])
|
from := resolvePath(hc, args[0])
|
||||||
@ -94,7 +115,7 @@ func installCompletionCmd(hc interp.HandlerContext, cmd string, args []string) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return shutils.InsufficientArgsError(cmd, 2, len(args))
|
return handlers.InsufficientArgsError(cmd, 2, len(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
shell := args[0]
|
shell := args[0]
|
||||||
@ -187,10 +208,12 @@ func getLibPrefix(hc interp.HandlerContext) string {
|
|||||||
|
|
||||||
architecture := hc.Env.Get("ARCH").Str
|
architecture := hc.Env.Get("ARCH").Str
|
||||||
|
|
||||||
if distroID == "debian" || slices.Contains(distroLike, "debian") {
|
if distroID == "debian" || slices.Contains(distroLike, "debian") ||
|
||||||
triple, ok := multiarchTupleMap[architecture]
|
distroID == "ubuntu" || slices.Contains(distroLike, "ubuntu") {
|
||||||
|
|
||||||
|
tuple, ok := multiarchTupleMap[architecture]
|
||||||
if ok {
|
if ok {
|
||||||
out = filepath.Join("/usr/lib", triple)
|
out = filepath.Join("/usr/lib", tuple)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +249,7 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
|||||||
|
|
||||||
hash := HEAD.Hash().String()
|
hash := HEAD.Hash().String()
|
||||||
|
|
||||||
fmt.Fprintf(hc.Stdout, "%d.%s", revNum, hash[:7])
|
fmt.Fprintf(hc.Stdout, "%d.%s\n", revNum, hash[:7])
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
@ -1,40 +0,0 @@
|
|||||||
package shutils_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"go.arsenm.dev/lure/internal/shutils"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNopExec(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
fl, err := syntax.NewParser().Parse(strings.NewReader(`/bin/echo test`), "lure.sh")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
runner, err := interp.New(
|
|
||||||
interp.ExecHandler(shutils.NopExec),
|
|
||||||
interp.StdIO(os.Stdin, buf, buf),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, fl)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.String() != "" {
|
|
||||||
t.Fatalf("Expected empty string, got %#v", buf.String())
|
|
||||||
}
|
|
||||||
}
|
|
155
internal/translations/files/lure.en.toml
Normal file
155
internal/translations/files/lure.en.toml
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
[[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 = 3121791194
|
||||||
|
value = 'Running LURE as root is forbidden as it may cause catastrophic damage to your system'
|
||||||
|
|
||||||
|
[[translation]]
|
||||||
|
id = 1256604213
|
||||||
|
value = 'Waiting for torrent metadata'
|
||||||
|
|
||||||
|
[[translation]]
|
||||||
|
id = 432261354
|
||||||
|
value = 'Downloading torrent file'
|
||||||
|
|
||||||
|
[[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'
|
151
internal/translations/files/lure.ru.toml
Normal file
151
internal/translations/files/lure.ru.toml
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
[[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 = 3121791194
|
||||||
|
value = 'Запуск LURE от имени root запрещен, так как это может привести к катастрофическому повреждению вашей системы'
|
||||||
|
|
||||||
|
[[translation]]
|
||||||
|
id = 1256604213
|
||||||
|
value = 'Ожидание метаданных торрента'
|
||||||
|
|
||||||
|
[[translation]]
|
||||||
|
id = 432261354
|
||||||
|
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 = 'протокол-скачивание'
|
56
internal/translations/translations.go
Normal file
56
internal/translations/translations.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 translations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"embed"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.elara.ws/logger"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
"go.elara.ws/translate"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed files
|
||||||
|
var translationFS embed.FS
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
translator *translate.Translator
|
||||||
|
)
|
||||||
|
|
||||||
|
func Translator(ctx context.Context) *translate.Translator {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
if translator == nil {
|
||||||
|
t, err := translate.NewFromFS(translationFS)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error creating new translator").Err(err).Send()
|
||||||
|
}
|
||||||
|
translator = &t
|
||||||
|
}
|
||||||
|
return translator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger(ctx context.Context, l logger.Logger, lang language.Tag) *translate.TranslatedLogger {
|
||||||
|
return translate.NewLogger(l, *Translator(ctx), lang)
|
||||||
|
}
|
70
internal/types/build.go
Normal file
70
internal/types/build.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
||||||
|
|
||||||
|
import "lure.sh/lure/pkg/manager"
|
||||||
|
|
||||||
|
type BuildOpts struct {
|
||||||
|
Script string
|
||||||
|
Manager manager.Manager
|
||||||
|
Clean bool
|
||||||
|
Interactive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildVars represents the script variables required
|
||||||
|
// to build a package
|
||||||
|
type BuildVars struct {
|
||||||
|
Name string `sh:"name,required"`
|
||||||
|
Version string `sh:"version,required"`
|
||||||
|
Release int `sh:"release,required"`
|
||||||
|
Epoch uint `sh:"epoch"`
|
||||||
|
Description string `sh:"desc"`
|
||||||
|
Homepage string `sh:"homepage"`
|
||||||
|
Maintainer string `sh:"maintainer"`
|
||||||
|
Architectures []string `sh:"architectures"`
|
||||||
|
Licenses []string `sh:"license"`
|
||||||
|
Provides []string `sh:"provides"`
|
||||||
|
Conflicts []string `sh:"conflicts"`
|
||||||
|
Depends []string `sh:"deps"`
|
||||||
|
BuildDepends []string `sh:"build_deps"`
|
||||||
|
OptDepends []string `sh:"opt_deps"`
|
||||||
|
Replaces []string `sh:"replaces"`
|
||||||
|
Sources []string `sh:"sources"`
|
||||||
|
Checksums []string `sh:"checksums"`
|
||||||
|
Backup []string `sh:"backup"`
|
||||||
|
Scripts Scripts `sh:"scripts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Scripts struct {
|
||||||
|
PreInstall string `sh:"preinstall"`
|
||||||
|
PostInstall string `sh:"postinstall"`
|
||||||
|
PreRemove string `sh:"preremove"`
|
||||||
|
PostRemove string `sh:"postremove"`
|
||||||
|
PreUpgrade string `sh:"preupgrade"`
|
||||||
|
PostUpgrade string `sh:"postupgrade"`
|
||||||
|
PreTrans string `sh:"pretrans"`
|
||||||
|
PostTrans string `sh:"posttrans"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Directories struct {
|
||||||
|
BaseDir string
|
||||||
|
SrcDir string
|
||||||
|
PkgDir string
|
||||||
|
ScriptDir string
|
||||||
|
}
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package types
|
||||||
|
|
||||||
// Config represents the LURE configuration file
|
// Config represents the LURE configuration file
|
||||||
@ -6,6 +24,7 @@ type Config struct {
|
|||||||
PagerStyle string `toml:"pagerStyle"`
|
PagerStyle string `toml:"pagerStyle"`
|
||||||
IgnorePkgUpdates []string `toml:"ignorePkgUpdates"`
|
IgnorePkgUpdates []string `toml:"ignorePkgUpdates"`
|
||||||
Repos []Repo `toml:"repo"`
|
Repos []Repo `toml:"repo"`
|
||||||
|
Unsafe Unsafe `toml:"unsafe"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repo represents a LURE repo within a configuration file
|
// Repo represents a LURE repo within a configuration file
|
||||||
@ -13,3 +32,7 @@ type Repo struct {
|
|||||||
Name string `toml:"name"`
|
Name string `toml:"name"`
|
||||||
URL string `toml:"url"`
|
URL string `toml:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Unsafe struct {
|
||||||
|
AllowRunAsRoot bool `toml:"allowRunAsRoot"`
|
||||||
|
}
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package types
|
||||||
|
|
||||||
// RepoConfig represents a LURE repo's lure-repo.toml file.
|
// RepoConfig represents a LURE repo's lure-repo.toml file.
|
||||||
|
118
list.go
118
list.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -22,67 +22,87 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.arsenm.dev/logger/log"
|
"lure.sh/lure/internal/config"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/internal/db"
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
"go.arsenm.dev/lure/manager"
|
"lure.sh/lure/pkg/manager"
|
||||||
|
"lure.sh/lure/pkg/repos"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func listCmd(c *cli.Context) error {
|
var listCmd = &cli.Command{
|
||||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
Name: "list",
|
||||||
if err != nil {
|
Usage: "List LURE repo packages",
|
||||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
Aliases: []string{"ls"},
|
||||||
}
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "installed",
|
||||||
|
Aliases: []string{"I"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
where := "true"
|
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
||||||
args := []any(nil)
|
|
||||||
if c.NArg() > 0 {
|
|
||||||
where = "name LIKE ? OR json_array_contains(provides, ?)"
|
|
||||||
args = []any{c.Args().First(), c.Args().First()}
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.GetPkgs(gdb, where, args...)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error getting packages").Err(err).Send()
|
|
||||||
}
|
|
||||||
defer result.Close()
|
|
||||||
|
|
||||||
var installed map[string]string
|
|
||||||
if c.Bool("installed") {
|
|
||||||
mgr := manager.Detect()
|
|
||||||
if mgr == nil {
|
|
||||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error listing installed packages").Err(err).Send()
|
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for result.Next() {
|
where := "true"
|
||||||
var pkg db.Package
|
args := []any(nil)
|
||||||
err := result.StructScan(&pkg)
|
if c.NArg() > 0 {
|
||||||
|
where = "name LIKE ? OR json_array_contains(provides, ?)"
|
||||||
|
args = []any{c.Args().First(), c.Args().First()}
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := db.GetPkgs(ctx, where, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal("Error getting packages").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
defer result.Close()
|
||||||
|
|
||||||
version := pkg.Version
|
var installed map[string]string
|
||||||
if c.Bool("installed") {
|
if c.Bool("installed") {
|
||||||
instVersion, ok := installed[pkg.Name]
|
mgr := manager.Detect()
|
||||||
if !ok {
|
if mgr == nil {
|
||||||
return nil
|
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||||
} else {
|
}
|
||||||
version = instVersion
|
|
||||||
|
installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error listing installed packages").Err(err).Send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version)
|
for result.Next() {
|
||||||
}
|
var pkg db.Package
|
||||||
|
err := result.StructScan(&pkg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkg.Name) {
|
||||||
log.Fatal("Error iterating over packages").Err(err).Send()
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
version := pkg.Version
|
||||||
|
if c.Bool("installed") {
|
||||||
|
instVersion, ok := installed[pkg.Name]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
version = instVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error iterating over packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
247
main.go
247
main.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -20,197 +20,96 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.arsenm.dev/logger"
|
"go.elara.ws/logger"
|
||||||
"go.arsenm.dev/logger/log"
|
"lure.sh/lure/internal/config"
|
||||||
"go.arsenm.dev/lure/internal/config"
|
"lure.sh/lure/internal/db"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/internal/translations"
|
||||||
"go.arsenm.dev/lure/manager"
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
"lure.sh/lure/pkg/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate scripts/gen-version.sh
|
var app = &cli.App{
|
||||||
|
Name: "lure",
|
||||||
|
Usage: "Linux User REpository",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "pm-args",
|
||||||
|
Aliases: []string{"P"},
|
||||||
|
Usage: "Arguments to be passed on to the package manager",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "interactive",
|
||||||
|
Aliases: []string{"i"},
|
||||||
|
Value: isatty.IsTerminal(os.Stdin.Fd()),
|
||||||
|
Usage: "Enable interactive questions and prompts",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
installCmd,
|
||||||
|
removeCmd,
|
||||||
|
upgradeCmd,
|
||||||
|
infoCmd,
|
||||||
|
listCmd,
|
||||||
|
buildCmd,
|
||||||
|
addrepoCmd,
|
||||||
|
removerepoCmd,
|
||||||
|
refreshCmd,
|
||||||
|
fixCmd,
|
||||||
|
genCmd,
|
||||||
|
helperCmd,
|
||||||
|
versionCmd,
|
||||||
|
},
|
||||||
|
Before: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
func init() {
|
cmd := c.Args().First()
|
||||||
log.Logger = logger.NewCLI(os.Stderr)
|
if cmd != "helper" && !config.Config(ctx).Unsafe.AllowRunAsRoot && os.Geteuid() == 0 {
|
||||||
|
log.Fatal("Running LURE as root is forbidden as it may cause catastrophic damage to your system").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
|
||||||
|
args := strings.Split(trimmed, " ")
|
||||||
|
manager.Args = append(manager.Args, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
After: func(ctx *cli.Context) error {
|
||||||
|
return db.Close()
|
||||||
|
},
|
||||||
|
EnableBashCompletion: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionCmd = &cli.Command{
|
||||||
|
Name: "version",
|
||||||
|
Usage: "Print the current LURE version and exit",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
println(config.Version)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
log := translations.NewLogger(ctx, logger.NewCLI(os.Stderr), config.Language(ctx))
|
||||||
|
ctx = loggerctx.With(ctx, log)
|
||||||
|
|
||||||
|
// Set the root command to the one set in the LURE config
|
||||||
|
manager.DefaultRootCmd = config.Config(ctx).RootCmd
|
||||||
|
|
||||||
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
// Exit the program after a maximum of 200ms
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
gdb.Close()
|
|
||||||
os.Exit(0)
|
|
||||||
}()
|
|
||||||
|
|
||||||
app := &cli.App{
|
|
||||||
Name: "lure",
|
|
||||||
Usage: "Linux User REpository",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "pm-args",
|
|
||||||
Aliases: []string{"P"},
|
|
||||||
Usage: "Arguments to be passed on to the package manager",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Commands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "install",
|
|
||||||
Usage: "Install a new package",
|
|
||||||
Aliases: []string{"in"},
|
|
||||||
Action: installCmd,
|
|
||||||
BashComplete: completionInstall,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "remove",
|
|
||||||
Usage: "Remove an installed package",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Action: removeCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "upgrade",
|
|
||||||
Usage: "Upgrade all installed packages",
|
|
||||||
Aliases: []string{"up"},
|
|
||||||
Action: upgradeCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "all",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Show all information, not just for the current distro",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "info",
|
|
||||||
Usage: "Print information about a package",
|
|
||||||
Action: infoCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "installed",
|
|
||||||
Aliases: []string{"I"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "list",
|
|
||||||
Usage: "List LURE repo packages",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Action: listCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "script",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Value: "lure.sh",
|
|
||||||
Usage: "Path to the build script",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "build",
|
|
||||||
Usage: "Build a local package",
|
|
||||||
Action: buildCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Required: true,
|
|
||||||
Usage: "Name of the new repo",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "url",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Required: true,
|
|
||||||
Usage: "URL of the new repo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "addrepo",
|
|
||||||
Usage: "Add a new repository",
|
|
||||||
Aliases: []string{"ar"},
|
|
||||||
Action: addrepoCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Required: true,
|
|
||||||
Usage: "Name of the repo to be deleted",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "removerepo",
|
|
||||||
Usage: "Remove an existing repository",
|
|
||||||
Aliases: []string{"rr"},
|
|
||||||
Action: removerepoCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "refresh",
|
|
||||||
Usage: "Pull all repositories that have changed",
|
|
||||||
Aliases: []string{"ref"},
|
|
||||||
Action: refreshCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "fix",
|
|
||||||
Usage: "Attempt to fix problems with LURE",
|
|
||||||
Action: fixCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Usage: "Display the current LURE version and exit",
|
|
||||||
Action: displayVersion,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Before: func(c *cli.Context) error {
|
|
||||||
args := strings.Split(c.String("pm-args"), " ")
|
|
||||||
if len(args) == 1 && args[0] == "" {
|
|
||||||
args = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.Args = append(manager.Args, args...)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
After: func(ctx *cli.Context) error {
|
|
||||||
return gdb.Close()
|
|
||||||
},
|
|
||||||
EnableBashCompletion: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := app.RunContext(ctx, os.Args)
|
err := app.RunContext(ctx, os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error while running app").Err(err).Send()
|
log.Error("Error while running app").Err(err).Send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayVersion(c *cli.Context) error {
|
|
||||||
print(config.Version)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func completionInstall(c *cli.Context) {
|
|
||||||
result, err := db.GetPkgs(gdb, "true")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error getting packages").Err(err).Send()
|
|
||||||
}
|
|
||||||
defer result.Close()
|
|
||||||
|
|
||||||
for result.Next() {
|
|
||||||
var pkg db.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error iterating over packages").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(pkg.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
838
pkg/build/build.go
Normal file
838
pkg/build/build.go
Normal file
@ -0,0 +1,838 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/apk"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/arch"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/deb"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/rpm"
|
||||||
|
|
||||||
|
"github.com/goreleaser/nfpm/v2"
|
||||||
|
"github.com/goreleaser/nfpm/v2/files"
|
||||||
|
"lure.sh/lure/internal/cliutils"
|
||||||
|
"lure.sh/lure/internal/config"
|
||||||
|
"lure.sh/lure/internal/cpu"
|
||||||
|
"lure.sh/lure/internal/db"
|
||||||
|
"lure.sh/lure/internal/dl"
|
||||||
|
"lure.sh/lure/internal/shutils/decoder"
|
||||||
|
"lure.sh/lure/internal/shutils/handlers"
|
||||||
|
"lure.sh/lure/internal/shutils/helpers"
|
||||||
|
"lure.sh/lure/internal/types"
|
||||||
|
"lure.sh/lure/pkg/distro"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
"lure.sh/lure/pkg/manager"
|
||||||
|
"lure.sh/lure/pkg/repos"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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, opts types.BuildOpts) ([]string, []string, error) {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
|
info, err := distro.ParseOSRelease(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fl, err := parseScript(info, opts.Script)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first pass is just used to get variable values and runs before
|
||||||
|
// the script is displayed, so it's restricted so as to prevent malicious
|
||||||
|
// code from executing.
|
||||||
|
vars, err := executeFirstPass(ctx, info, fl, opts.Script)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs := getDirs(ctx, vars, opts.Script)
|
||||||
|
|
||||||
|
// If opts.Clean isn't set and we find the package already built,
|
||||||
|
// just return it rather than rebuilding
|
||||||
|
if !opts.Clean {
|
||||||
|
builtPkgPath, ok, err := checkForBuiltPackage(opts.Manager, vars, getPkgFormat(opts.Manager), dirs.BaseDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return []string{builtPkgPath}, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the user if they'd like to see the build script
|
||||||
|
err = cliutils.PromptViewScript(ctx, opts.Script, vars.Name, config.Config(ctx).PagerStyle, opts.Interactive)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to prompt user to view build script").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
|
||||||
|
|
||||||
|
// The second pass will be used to execute the actual code,
|
||||||
|
// so it's unrestricted. The script has already been displayed
|
||||||
|
// to the user by this point, so it should be safe
|
||||||
|
dec, err := executeSecondPass(ctx, info, fl, dirs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the installed packages on the system
|
||||||
|
installed, err := opts.Manager.ListInstalled(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cont, err := performChecks(ctx, vars, opts.Interactive, installed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else if !cont {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the directories for building
|
||||||
|
err = prepareDirs(dirs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDeps, err := installBuildDeps(ctx, vars, opts, installed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = installOptDeps(ctx, vars, opts, installed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
builtPaths, builtNames, repoDeps, err := buildLUREDeps(ctx, opts, vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Downloading sources").Send()
|
||||||
|
|
||||||
|
err = getSources(ctx, dirs, vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = executeFunctions(ctx, dec, dirs, vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Building package metadata").Str("name", vars.Name).Send()
|
||||||
|
|
||||||
|
pkgFormat := getPkgFormat(opts.Manager)
|
||||||
|
|
||||||
|
pkgInfo, err := buildPkgMetadata(vars, dirs, pkgFormat, append(repoDeps, builtNames...))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
packager, err := nfpm.Get(pkgFormat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgName := packager.ConventionalFileName(pkgInfo)
|
||||||
|
pkgPath := filepath.Join(dirs.BaseDir, pkgName)
|
||||||
|
|
||||||
|
pkgFile, err := os.Create(pkgPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Compressing package").Str("name", pkgName).Send()
|
||||||
|
|
||||||
|
err = packager.Package(pkgInfo, pkgFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = removeBuildDeps(ctx, buildDeps, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the path and name of the package we just built to the
|
||||||
|
// appropriate slices
|
||||||
|
pkgPaths := append(builtPaths, pkgPath)
|
||||||
|
pkgNames := append(builtNames, vars.Name)
|
||||||
|
|
||||||
|
// Remove any duplicates from the pkgPaths and pkgNames.
|
||||||
|
// Duplicates can be introduced if several of the dependencies
|
||||||
|
// depend on the same packages.
|
||||||
|
pkgPaths = removeDuplicates(pkgPaths)
|
||||||
|
pkgNames = removeDuplicates(pkgNames)
|
||||||
|
|
||||||
|
return pkgPaths, pkgNames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseScript parses the build script using the built-in bash implementation
|
||||||
|
func parseScript(info *distro.OSRelease, script string) (*syntax.File, error) {
|
||||||
|
fl, err := os.Open(script)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fl.Close()
|
||||||
|
|
||||||
|
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeFirstPass executes the parsed script in a restricted environment
|
||||||
|
// to extract the build variables without executing any actual code.
|
||||||
|
func executeFirstPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, script string) (*types.BuildVars, error) {
|
||||||
|
scriptDir := filepath.Dir(script)
|
||||||
|
env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir})
|
||||||
|
|
||||||
|
runner, err := interp.New(
|
||||||
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
|
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||||
|
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)),
|
||||||
|
interp.ReadDirHandler(handlers.RestrictedReadDir(scriptDir)),
|
||||||
|
interp.StatHandler(handlers.RestrictedStat(scriptDir)),
|
||||||
|
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runner.Run(ctx, fl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := decoder.New(info, runner)
|
||||||
|
|
||||||
|
var vars types.BuildVars
|
||||||
|
err = dec.DecodeVars(&vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &vars, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDirs returns the appropriate directories for the script
|
||||||
|
func getDirs(ctx context.Context, vars *types.BuildVars, script string) types.Directories {
|
||||||
|
baseDir := filepath.Join(config.GetPaths(ctx).PkgsDir, vars.Name)
|
||||||
|
return types.Directories{
|
||||||
|
BaseDir: baseDir,
|
||||||
|
SrcDir: filepath.Join(baseDir, "src"),
|
||||||
|
PkgDir: filepath.Join(baseDir, "pkg"),
|
||||||
|
ScriptDir: filepath.Dir(script),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeSecondPass executes the build script for the second time, this time without any restrictions.
|
||||||
|
// It returns a decoder that can be used to retrieve functions and variables from the script.
|
||||||
|
func executeSecondPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, dirs types.Directories) (*decoder.Decoder, error) {
|
||||||
|
env := createBuildEnvVars(info, dirs)
|
||||||
|
|
||||||
|
fakeroot := handlers.FakerootExecHandler(2 * time.Second)
|
||||||
|
runner, err := interp.New(
|
||||||
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
|
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||||
|
interp.ExecHandler(helpers.Helpers.ExecHandler(fakeroot)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runner.Run(ctx, fl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoder.New(info, runner), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareDirs prepares the directories for building.
|
||||||
|
func prepareDirs(dirs types.Directories) error {
|
||||||
|
err := os.RemoveAll(dirs.BaseDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(dirs.SrcDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.MkdirAll(dirs.PkgDir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
// performChecks checks various things on the system to ensure that the package can be installed.
|
||||||
|
func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool, installed map[string]string) (bool, error) {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) {
|
||||||
|
cont, err := cliutils.YesNoPrompt(ctx, "Your system's CPU architecture doesn't match this package. Do you want to build anyway?", interactive, true)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cont {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if instVer, ok := installed[vars.Name]; ok {
|
||||||
|
log.Warn("This package is already installed").
|
||||||
|
Str("name", vars.Name).
|
||||||
|
Str("version", instVer).
|
||||||
|
Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// installBuildDeps installs any build dependencies that aren't already installed and returns
|
||||||
|
// a slice containing the names of all the packages it installed.
|
||||||
|
func installBuildDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) ([]string, error) {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
var buildDeps []string
|
||||||
|
if len(vars.BuildDepends) > 0 {
|
||||||
|
found, notFound, err := repos.FindPkgs(ctx, vars.BuildDepends)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
found = removeAlreadyInstalled(found, installed)
|
||||||
|
|
||||||
|
log.Info("Installing build dependencies").Send()
|
||||||
|
|
||||||
|
flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
|
||||||
|
buildDeps = packageNames(flattened)
|
||||||
|
InstallPkgs(ctx, flattened, notFound, opts)
|
||||||
|
}
|
||||||
|
return buildDeps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// installOptDeps asks the user which, if any, optional dependencies they want to install.
|
||||||
|
// If the user chooses to install any optional dependencies, it performs the installation.
|
||||||
|
func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) error {
|
||||||
|
if len(vars.OptDepends) > 0 {
|
||||||
|
optDeps, err := cliutils.ChooseOptDepends(ctx, vars.OptDepends, "install", opts.Interactive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(optDeps) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
found, notFound, err := repos.FindPkgs(ctx, optDeps)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
found = removeAlreadyInstalled(found, installed)
|
||||||
|
flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
|
||||||
|
InstallPkgs(ctx, flattened, notFound, opts)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildLUREDeps builds all the LURE dependencies of the package. It returns the paths and names
|
||||||
|
// of the packages it built, as well as all the dependencies it didn't find in the LURE repo so
|
||||||
|
// they can be installed from the system repos.
|
||||||
|
func buildLUREDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVars) (builtPaths, builtNames, repoDeps []string, err error) {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
if len(vars.Depends) > 0 {
|
||||||
|
log.Info("Installing dependencies").Send()
|
||||||
|
|
||||||
|
found, notFound, err := repos.FindPkgs(ctx, vars.Depends)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
repoDeps = notFound
|
||||||
|
|
||||||
|
// If there are multiple options for some packages, flatten them all into a single slice
|
||||||
|
pkgs := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
|
||||||
|
scripts := GetScriptPaths(ctx, pkgs)
|
||||||
|
for _, script := range scripts {
|
||||||
|
newOpts := opts
|
||||||
|
newOpts.Script = script
|
||||||
|
|
||||||
|
// Build the dependency
|
||||||
|
pkgPaths, pkgNames, err := BuildPackage(ctx, newOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the paths of all the built packages to builtPaths
|
||||||
|
builtPaths = append(builtPaths, pkgPaths...)
|
||||||
|
// Append the names of all the built packages to builtNames
|
||||||
|
builtNames = append(builtNames, pkgNames...)
|
||||||
|
// Append the name of the current package to builtNames
|
||||||
|
builtNames = append(builtNames, filepath.Base(filepath.Dir(script)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any potential duplicates, which can be introduced if
|
||||||
|
// several of the dependencies depend on the same packages.
|
||||||
|
repoDeps = removeDuplicates(repoDeps)
|
||||||
|
builtPaths = removeDuplicates(builtPaths)
|
||||||
|
builtNames = removeDuplicates(builtNames)
|
||||||
|
return builtPaths, builtNames, repoDeps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeFunctions executes the special LURE functions, such as version(), prepare(), etc.
|
||||||
|
func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Directories, vars *types.BuildVars) (err error) {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
version, ok := dec.GetFunc("version")
|
||||||
|
if ok {
|
||||||
|
log.Info("Executing version()").Send()
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
err = version(
|
||||||
|
ctx,
|
||||||
|
interp.Dir(dirs.SrcDir),
|
||||||
|
interp.StdIO(os.Stdin, buf, os.Stderr),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newVer := strings.TrimSpace(buf.String())
|
||||||
|
err = setVersion(ctx, dec.Runner, newVer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vars.Version = newVer
|
||||||
|
|
||||||
|
log.Info("Updating version").Str("new", newVer).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare, ok := dec.GetFunc("prepare")
|
||||||
|
if ok {
|
||||||
|
log.Info("Executing prepare()").Send()
|
||||||
|
|
||||||
|
err = prepare(ctx, interp.Dir(dirs.SrcDir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build, ok := dec.GetFunc("build")
|
||||||
|
if ok {
|
||||||
|
log.Info("Executing build()").Send()
|
||||||
|
|
||||||
|
err = build(ctx, interp.Dir(dirs.SrcDir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packageFn, ok := dec.GetFunc("package")
|
||||||
|
if ok {
|
||||||
|
log.Info("Executing package()").Send()
|
||||||
|
|
||||||
|
err = packageFn(ctx, interp.Dir(dirs.SrcDir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Fatal("The package() function is required").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPkgMetadata builds the metadata for the package that's going to be built.
|
||||||
|
func buildPkgMetadata(vars *types.BuildVars, dirs types.Directories, pkgFormat string, deps []string) (*nfpm.Info, error) {
|
||||||
|
pkgInfo := &nfpm.Info{
|
||||||
|
Name: vars.Name,
|
||||||
|
Description: vars.Description,
|
||||||
|
Arch: cpu.Arch(),
|
||||||
|
Platform: "linux",
|
||||||
|
Version: vars.Version,
|
||||||
|
Release: strconv.Itoa(vars.Release),
|
||||||
|
Homepage: vars.Homepage,
|
||||||
|
License: strings.Join(vars.Licenses, ", "),
|
||||||
|
Maintainer: vars.Maintainer,
|
||||||
|
Overridables: nfpm.Overridables{
|
||||||
|
Conflicts: vars.Conflicts,
|
||||||
|
Replaces: vars.Replaces,
|
||||||
|
Provides: vars.Provides,
|
||||||
|
Depends: deps,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkgFormat == "apk" {
|
||||||
|
// Alpine refuses to install packages that provide themselves, so remove any such provides
|
||||||
|
pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
|
||||||
|
return s == pkgInfo.Name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.Epoch != 0 {
|
||||||
|
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
setScripts(vars, pkgInfo, dirs.ScriptDir)
|
||||||
|
|
||||||
|
if slices.Contains(vars.Architectures, "all") {
|
||||||
|
pkgInfo.Arch = "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err := buildContents(vars, dirs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pkgInfo.Overridables.Contents = contents
|
||||||
|
|
||||||
|
return pkgInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildContents builds the contents section of the package, which contains the files
|
||||||
|
// that will be placed into the final package.
|
||||||
|
func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Content, error) {
|
||||||
|
contents := []*files.Content{}
|
||||||
|
err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
trimmed := strings.TrimPrefix(path, dirs.PkgDir)
|
||||||
|
|
||||||
|
if fi.IsDir() {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the directory is empty, skip it
|
||||||
|
_, err = f.Readdirnames(1)
|
||||||
|
if err != io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = append(contents, &files.Content{
|
||||||
|
Source: path,
|
||||||
|
Destination: trimmed,
|
||||||
|
Type: "dir",
|
||||||
|
FileInfo: &files.ContentFileInfo{
|
||||||
|
MTime: fi.ModTime(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
link, err := os.Readlink(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Remove pkgdir from the symlink's path
|
||||||
|
link = strings.TrimPrefix(link, dirs.PkgDir)
|
||||||
|
|
||||||
|
contents = append(contents, &files.Content{
|
||||||
|
Source: link,
|
||||||
|
Destination: trimmed,
|
||||||
|
Type: "symlink",
|
||||||
|
FileInfo: &files.ContentFileInfo{
|
||||||
|
MTime: fi.ModTime(),
|
||||||
|
Mode: fi.Mode(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fileContent := &files.Content{
|
||||||
|
Source: path,
|
||||||
|
Destination: trimmed,
|
||||||
|
FileInfo: &files.ContentFileInfo{
|
||||||
|
MTime: fi.ModTime(),
|
||||||
|
Mode: fi.Mode(),
|
||||||
|
Size: fi.Size(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the file is supposed to be backed up, set its type to config|noreplace
|
||||||
|
if slices.Contains(vars.Backup, trimmed) {
|
||||||
|
fileContent.Type = "config|noreplace"
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = append(contents, fileContent)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return contents, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeBuildDeps asks the user if they'd like to remove the build dependencies that were
|
||||||
|
// installed by installBuildDeps. If so, it uses the package manager to do that.
|
||||||
|
func removeBuildDeps(ctx context.Context, buildDeps []string, opts types.BuildOpts) error {
|
||||||
|
if len(buildDeps) > 0 {
|
||||||
|
remove, err := cliutils.YesNoPrompt(ctx, "Would you like to remove the build dependencies?", opts.Interactive, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
err = opts.Manager.Remove(
|
||||||
|
&manager.Opts{
|
||||||
|
AsRoot: true,
|
||||||
|
NoConfirm: true,
|
||||||
|
},
|
||||||
|
buildDeps...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkForBuiltPackage tries to detect a previously-built package and returns its path
|
||||||
|
// and true if it finds one. If it doesn't find it, it returns "", false, nil.
|
||||||
|
func checkForBuiltPackage(mgr manager.Manager, vars *types.BuildVars, pkgFormat, baseDir string) (string, bool, error) {
|
||||||
|
filename, err := pkgFileName(vars, pkgFormat)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgPath := filepath.Join(baseDir, filename)
|
||||||
|
|
||||||
|
_, err = os.Stat(pkgPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkgPath, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pkgFileName returns the filename of the package if it were to be built.
|
||||||
|
// This is used to check if the package has already been built.
|
||||||
|
func pkgFileName(vars *types.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPkgFormat returns the package format of the package manager,
|
||||||
|
// or LURE_PKG_FORMAT if that's set.
|
||||||
|
func getPkgFormat(mgr manager.Manager) string {
|
||||||
|
pkgFormat := mgr.Format()
|
||||||
|
if format, ok := os.LookupEnv("LURE_PKG_FORMAT"); ok {
|
||||||
|
pkgFormat = format
|
||||||
|
}
|
||||||
|
return pkgFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// createBuildEnvVars creates the environment variables that will be set in the
|
||||||
|
// build script when it's executed.
|
||||||
|
func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string {
|
||||||
|
env := os.Environ()
|
||||||
|
|
||||||
|
env = append(
|
||||||
|
env,
|
||||||
|
"DISTRO_NAME="+info.Name,
|
||||||
|
"DISTRO_PRETTY_NAME="+info.PrettyName,
|
||||||
|
"DISTRO_ID="+info.ID,
|
||||||
|
"DISTRO_VERSION_ID="+info.VersionID,
|
||||||
|
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
||||||
|
"ARCH="+cpu.Arch(),
|
||||||
|
"NCPU="+strconv.Itoa(runtime.NumCPU()),
|
||||||
|
)
|
||||||
|
|
||||||
|
if dirs.ScriptDir != "" {
|
||||||
|
env = append(env, "scriptdir="+dirs.ScriptDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dirs.PkgDir != "" {
|
||||||
|
env = append(env, "pkgdir="+dirs.PkgDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dirs.SrcDir != "" {
|
||||||
|
env = append(env, "srcdir="+dirs.SrcDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSources downloads the sources from the script.
|
||||||
|
func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
if len(bv.Sources) != len(bv.Checksums) {
|
||||||
|
log.Fatal("The checksums array must be the same length as sources").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, src := range bv.Sources {
|
||||||
|
opts := dl.Options{
|
||||||
|
Name: fmt.Sprintf("%s[%d]", bv.Name, i),
|
||||||
|
URL: src,
|
||||||
|
Destination: dirs.SrcDir,
|
||||||
|
Progress: os.Stderr,
|
||||||
|
LocalDir: dirs.ScriptDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
|
||||||
|
// If the checksum contains a colon, use the part before the colon
|
||||||
|
// as the algorithm and the part after as the actual checksum.
|
||||||
|
// Otherwise, use the default sha256 with the whole string as the checksum.
|
||||||
|
algo, hashData, ok := strings.Cut(bv.Checksums[i], ":")
|
||||||
|
if ok {
|
||||||
|
checksum, err := hex.DecodeString(hashData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.Hash = checksum
|
||||||
|
opts.HashAlgorithm = algo
|
||||||
|
} else {
|
||||||
|
checksum, err := hex.DecodeString(bv.Checksums[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.Hash = checksum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dl.Download(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setScripts adds any hook scripts to the package metadata.
|
||||||
|
func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) {
|
||||||
|
if vars.Scripts.PreInstall != "" {
|
||||||
|
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.Scripts.PostInstall != "" {
|
||||||
|
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.Scripts.PreRemove != "" {
|
||||||
|
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.Scripts.PostRemove != "" {
|
||||||
|
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.Scripts.PreUpgrade != "" {
|
||||||
|
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
||||||
|
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.Scripts.PostUpgrade != "" {
|
||||||
|
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
||||||
|
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.Scripts.PreTrans != "" {
|
||||||
|
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.Scripts.PostTrans != "" {
|
||||||
|
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setVersion changes the version variable in the script runner.
|
||||||
|
// It's used to set the version to the output of the version() function.
|
||||||
|
func setVersion(ctx context.Context, r *interp.Runner, to string) error {
|
||||||
|
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.Run(ctx, fl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeAlreadyInstalled returns a map without any dependencies that are already installed
|
||||||
|
func removeAlreadyInstalled(found map[string][]db.Package, installed map[string]string) map[string][]db.Package {
|
||||||
|
filteredPackages := make(map[string][]db.Package)
|
||||||
|
|
||||||
|
for name, pkgList := range found {
|
||||||
|
filteredPkgList := []db.Package{}
|
||||||
|
for _, pkg := range pkgList {
|
||||||
|
if _, isInstalled := installed[pkg.Name]; !isInstalled {
|
||||||
|
filteredPkgList = append(filteredPkgList, pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filteredPackages[name] = filteredPkgList
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredPackages
|
||||||
|
}
|
||||||
|
|
||||||
|
// packageNames returns the names of all the given packages
|
||||||
|
func packageNames(pkgs []db.Package) []string {
|
||||||
|
names := make([]string, len(pkgs))
|
||||||
|
for i, p := range pkgs {
|
||||||
|
names[i] = p.Name
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeDuplicates removes any duplicates from the given slice
|
||||||
|
func removeDuplicates(slice []string) []string {
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
result := []string{}
|
||||||
|
|
||||||
|
for _, s := range slice {
|
||||||
|
if _, ok := seen[s]; !ok {
|
||||||
|
seen[s] = struct{}{}
|
||||||
|
result = append(result, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
72
pkg/build/install.go
Normal file
72
pkg/build/install.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"lure.sh/lure/internal/config"
|
||||||
|
"lure.sh/lure/internal/db"
|
||||||
|
"lure.sh/lure/internal/types"
|
||||||
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InstallPkgs installs native packages via the package manager,
|
||||||
|
// then builds and installs the LURE packages
|
||||||
|
func InstallPkgs(ctx context.Context, lurePkgs []db.Package, nativePkgs []string, opts types.BuildOpts) {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
|
if len(nativePkgs) > 0 {
|
||||||
|
err := opts.Manager.Install(nil, nativePkgs...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error installing native packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallScripts(ctx, GetScriptPaths(ctx, lurePkgs), opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScriptPaths returns a slice of script paths corresponding to the
|
||||||
|
// given packages
|
||||||
|
func GetScriptPaths(ctx context.Context, pkgs []db.Package) []string {
|
||||||
|
var scripts []string
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, pkg.Repository, pkg.Name, "lure.sh")
|
||||||
|
scripts = append(scripts, scriptPath)
|
||||||
|
}
|
||||||
|
return scripts
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallScripts builds and installs the given LURE build scripts
|
||||||
|
func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
for _, script := range scripts {
|
||||||
|
opts.Script = script
|
||||||
|
builtPkgs, _, err := BuildPackage(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error building package").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = opts.Manager.InstallLocal(nil, builtPkgs...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error installing package").Err(err).Send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -20,18 +20,16 @@ package distro
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.arsenm.dev/lure/internal/shutils"
|
"lure.sh/lure/internal/shutils/handlers"
|
||||||
"mvdan.cc/sh/v3/expand"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrParse = errors.New("could not parse os-release file")
|
// OSRelease contains information from an os-release file
|
||||||
|
|
||||||
type OSRelease struct {
|
type OSRelease struct {
|
||||||
Name string
|
Name string
|
||||||
PrettyName string
|
PrettyName string
|
||||||
@ -50,7 +48,8 @@ var parsed *OSRelease
|
|||||||
|
|
||||||
// OSReleaseName returns a struct parsed from the system's os-release
|
// OSReleaseName returns a struct parsed from the system's os-release
|
||||||
// file. It checks /etc/os-release as well as /usr/lib/os-release.
|
// file. It checks /etc/os-release as well as /usr/lib/os-release.
|
||||||
// The returned OSRelease struct is a singleton.
|
// The first time it's called, it'll parse the os-release file.
|
||||||
|
// Subsequent calls will return the same value.
|
||||||
func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
||||||
if parsed != nil {
|
if parsed != nil {
|
||||||
return parsed, nil
|
return parsed, nil
|
||||||
@ -75,10 +74,10 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
|||||||
// as well as no environment variables in order to prevent vulnerabilities
|
// as well as no environment variables in order to prevent vulnerabilities
|
||||||
// caused by changing the os-release file.
|
// caused by changing the os-release file.
|
||||||
runner, err := interp.New(
|
runner, err := interp.New(
|
||||||
interp.OpenHandler(shutils.NopOpen),
|
interp.OpenHandler(handlers.NopOpen),
|
||||||
interp.ExecHandler(shutils.NopExec),
|
interp.ExecHandler(handlers.NopExec),
|
||||||
interp.ReadDirHandler(shutils.NopReadDir),
|
interp.ReadDirHandler(handlers.NopReadDir),
|
||||||
interp.StatHandler(shutils.NopStat),
|
interp.StatHandler(handlers.NopStat),
|
||||||
interp.Env(expand.ListEnviron()),
|
interp.Env(expand.ListEnviron()),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -87,7 +86,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
|||||||
|
|
||||||
err = runner.Run(ctx, file)
|
err = runner.Run(ctx, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrParse
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out := &OSRelease{
|
out := &OSRelease{
|
||||||
@ -103,7 +102,14 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
|||||||
Logo: runner.Vars["LOGO"].Str,
|
Logo: runner.Vars["LOGO"].Str,
|
||||||
}
|
}
|
||||||
|
|
||||||
if runner.Vars["ID_LIKE"].IsSet() {
|
distroUpdated := false
|
||||||
|
if distID, ok := os.LookupEnv("LURE_DISTRO"); ok {
|
||||||
|
out.ID = distID
|
||||||
|
}
|
||||||
|
|
||||||
|
if distLike, ok := os.LookupEnv("LURE_DISTRO_LIKE"); ok {
|
||||||
|
out.Like = strings.Split(distLike, " ")
|
||||||
|
} else if runner.Vars["ID_LIKE"].IsSet() && !distroUpdated {
|
||||||
out.Like = strings.Split(runner.Vars["ID_LIKE"].Str, " ")
|
out.Like = strings.Split(runner.Vars["ID_LIKE"].Str, " ")
|
||||||
}
|
}
|
||||||
|
|
13
pkg/gen/funcs.go
Normal file
13
pkg/gen/funcs.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var funcs = template.FuncMap{
|
||||||
|
"tolower": strings.ToLower,
|
||||||
|
"firstchar": func(s string) string {
|
||||||
|
return s[:1]
|
||||||
|
},
|
||||||
|
}
|
84
pkg/gen/pip.go
Normal file
84
pkg/gen/pip.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed tmpls/pip.tmpl.sh
|
||||||
|
var pipTmpl string
|
||||||
|
|
||||||
|
type PipOptions struct {
|
||||||
|
Name string
|
||||||
|
Version string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type pypiAPIResponse struct {
|
||||||
|
Info pypiInfo `json:"info"`
|
||||||
|
URLs []pypiURL `json:"urls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res pypiAPIResponse) SourceURL() (pypiURL, error) {
|
||||||
|
for _, url := range res.URLs {
|
||||||
|
if url.PackageType == "sdist" {
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pypiURL{}, errors.New("package doesn't have a source distribution")
|
||||||
|
}
|
||||||
|
|
||||||
|
type pypiInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Summary string `json:"summary"`
|
||||||
|
Homepage string `json:"home_page"`
|
||||||
|
License string `json:"license"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pypiURL struct {
|
||||||
|
Digests map[string]string `json:"digests"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
PackageType string `json:"packagetype"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pip(w io.Writer, opts PipOptions) error {
|
||||||
|
tmpl, err := template.New("pip").
|
||||||
|
Funcs(funcs).
|
||||||
|
Parse(pipTmpl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"https://pypi.org/pypi/%s/%s/json",
|
||||||
|
opts.Name,
|
||||||
|
opts.Version,
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("pypi: %s", res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp pypiAPIResponse
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Description != "" {
|
||||||
|
resp.Info.Summary = opts.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpl.Execute(w, resp)
|
||||||
|
}
|
31
pkg/gen/tmpls/pip.tmpl.sh
Normal file
31
pkg/gen/tmpls/pip.tmpl.sh
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
name='{{.Info.Name | tolower}}'
|
||||||
|
version='{{.Info.Version}}'
|
||||||
|
release='1'
|
||||||
|
desc='{{.Info.Summary}}'
|
||||||
|
homepage='{{.Info.Homepage}}'
|
||||||
|
maintainer='Example <user@example.com>'
|
||||||
|
architectures=('all')
|
||||||
|
license=('{{if .Info.License | ne ""}}{{.Info.License}}{{else}}custom:Unknown{{end}}')
|
||||||
|
provides=('{{.Info.Name | tolower}}')
|
||||||
|
conflicts=('{{.Info.Name | tolower}}')
|
||||||
|
|
||||||
|
deps=("python3")
|
||||||
|
deps_arch=("python")
|
||||||
|
deps_alpine=("python3")
|
||||||
|
|
||||||
|
build_deps=("python3" "python3-setuptools")
|
||||||
|
build_deps_arch=("python" "python-setuptools")
|
||||||
|
build_deps_alpine=("python3" "py3-setuptools")
|
||||||
|
|
||||||
|
sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}")
|
||||||
|
checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}')
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "$srcdir/{{.Info.Name}}-${version}"
|
||||||
|
python3 setup.py build
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "$srcdir/{{.Info.Name}}-${version}"
|
||||||
|
python3 setup.py install --root="${pkgdir}/" --optimize=1 || return 1
|
||||||
|
}
|
29
pkg/loggerctx/log.go
Normal file
29
pkg/loggerctx/log.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package loggerctx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.elara.ws/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loggerCtxKey is used as the context key for loggers
|
||||||
|
type loggerCtxKey struct{}
|
||||||
|
|
||||||
|
// With returns a copy of ctx containing log
|
||||||
|
func With(ctx context.Context, log logger.Logger) context.Context {
|
||||||
|
return context.WithValue(ctx, loggerCtxKey{}, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// From attempts to get a logger from ctx. If ctx doesn't
|
||||||
|
// contain a logger, it returns a nop logger.
|
||||||
|
func From(ctx context.Context) logger.Logger {
|
||||||
|
if val := ctx.Value(loggerCtxKey{}); val != nil {
|
||||||
|
if log, ok := val.(logger.Logger); ok && log != nil {
|
||||||
|
return log
|
||||||
|
} else {
|
||||||
|
return logger.NewNop()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return logger.NewNop()
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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) {
|
func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
opts = ensureOpts(opts)
|
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command("apk", "list", "-I")
|
||||||
var cmd *exec.Cmd
|
|
||||||
if opts.AsRoot {
|
|
||||||
cmd = exec.Command(getRootCmd(a.rootCmd), "apk", "list", "-I")
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("apk", "list", "-I")
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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) {
|
func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
opts = ensureOpts(opts)
|
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command("dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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) {
|
func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
opts = ensureOpts(opts)
|
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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) {
|
func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
opts = ensureOpts(opts)
|
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command("pacman", "-Q")
|
||||||
var cmd *exec.Cmd
|
|
||||||
if opts.AsRoot {
|
|
||||||
cmd = exec.Command(getRootCmd(p.rootCmd), "pacman", "-Q")
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("pacman", "-Q")
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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) {
|
func (y *YUM) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
opts = ensureOpts(opts)
|
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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) {
|
func (z *Zypper) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
opts = ensureOpts(opts)
|
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
83
pkg/repos/find.go
Normal file
83
pkg/repos/find.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"lure.sh/lure/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindPkgs looks for packages matching the inputs inside the database.
|
||||||
|
// It returns a map that maps the package name input to any packages found for it.
|
||||||
|
// It also returns a slice that contains the names of all packages that were not found.
|
||||||
|
func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
|
||||||
|
found := map[string][]db.Package{}
|
||||||
|
notFound := []string(nil)
|
||||||
|
|
||||||
|
for _, pkgName := range pkgs {
|
||||||
|
if pkgName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := db.GetPkgs(ctx, "json_array_contains(provides, ?)", pkgName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
added := 0
|
||||||
|
for result.Next() {
|
||||||
|
var pkg db.Package
|
||||||
|
err = result.StructScan(&pkg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
added++
|
||||||
|
found[pkgName] = append(found[pkgName], pkg)
|
||||||
|
}
|
||||||
|
result.Close()
|
||||||
|
|
||||||
|
if added == 0 {
|
||||||
|
result, err := db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for result.Next() {
|
||||||
|
var pkg db.Package
|
||||||
|
err = result.StructScan(&pkg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
added++
|
||||||
|
found[pkgName] = append(found[pkgName], pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if added == 0 {
|
||||||
|
notFound = append(notFound, pkgName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found, notFound, nil
|
||||||
|
}
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package repos_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -6,30 +24,24 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"lure.sh/lure/internal/db"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/internal/types"
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
"lure.sh/lure/pkg/repos"
|
||||||
"go.arsenm.dev/lure/internal/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindPkgs(t *testing.T) {
|
func TestFindPkgs(t *testing.T) {
|
||||||
gdb, err := sqlx.Open("sqlite", ":memory:")
|
_, 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 {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
setCfgDirs(t)
|
setCfgDirs(t)
|
||||||
defer removeCacheDir(t)
|
defer removeCacheDir(t)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
err = repos.Pull(ctx, gdb, []types.Repo{
|
err = repos.Pull(ctx, []types.Repo{
|
||||||
{
|
{
|
||||||
Name: "default",
|
Name: "default",
|
||||||
URL: "https://github.com/Arsen6331/lure-repo.git",
|
URL: "https://github.com/Arsen6331/lure-repo.git",
|
||||||
@ -39,7 +51,7 @@ func TestFindPkgs(t *testing.T) {
|
|||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
found, notFound, err := repos.FindPkgs(gdb, []string{"itd", "nonexistentpackage1", "nonexistentpackage2"})
|
found, notFound, err := repos.FindPkgs([]string{"itd", "nonexistentpackage1", "nonexistentpackage2"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
@ -69,45 +81,46 @@ func TestFindPkgs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFindPkgsEmpty(t *testing.T) {
|
func TestFindPkgsEmpty(t *testing.T) {
|
||||||
gdb, err := sqlx.Open("sqlite", ":memory:")
|
_, 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 {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
setCfgDirs(t)
|
setCfgDirs(t)
|
||||||
defer removeCacheDir(t)
|
defer removeCacheDir(t)
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, db.Package{
|
err = db.InsertPackage(db.Package{
|
||||||
Name: "test1",
|
Name: "test1",
|
||||||
Repository: "default",
|
Repository: "default",
|
||||||
Version: "0.0.1",
|
Version: "0.0.1",
|
||||||
Release: 1,
|
Release: 1,
|
||||||
Description: "Test package 1",
|
Description: db.NewJSON(map[string]string{
|
||||||
Provides: db.NewJSON([]string{""}),
|
"en": "Test package 1",
|
||||||
|
"ru": "Проверочный пакет 1",
|
||||||
|
}),
|
||||||
|
Provides: db.NewJSON([]string{""}),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, db.Package{
|
err = db.InsertPackage(db.Package{
|
||||||
Name: "test2",
|
Name: "test2",
|
||||||
Repository: "default",
|
Repository: "default",
|
||||||
Version: "0.0.1",
|
Version: "0.0.1",
|
||||||
Release: 1,
|
Release: 1,
|
||||||
Description: "Test package 2",
|
Description: db.NewJSON(map[string]string{
|
||||||
Provides: db.NewJSON([]string{"test"}),
|
"en": "Test package 2",
|
||||||
|
"ru": "Проверочный пакет 2",
|
||||||
|
}),
|
||||||
|
Provides: db.NewJSON([]string{"test"}),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
found, notFound, err := repos.FindPkgs(gdb, []string{"test", ""})
|
found, notFound, err := repos.FindPkgs([]string{"test", ""})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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
|
package repos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -7,6 +25,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
@ -14,17 +33,15 @@ import (
|
|||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/format/diff"
|
"github.com/go-git/go-git/v5/plumbing/format/diff"
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
"go.arsenm.dev/logger/log"
|
"go.elara.ws/vercmp"
|
||||||
"go.arsenm.dev/lure/distro"
|
"lure.sh/lure/internal/config"
|
||||||
"go.arsenm.dev/lure/download"
|
"lure.sh/lure/internal/db"
|
||||||
"go.arsenm.dev/lure/internal/config"
|
"lure.sh/lure/internal/shutils/decoder"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/internal/shutils/handlers"
|
||||||
"go.arsenm.dev/lure/internal/shutils"
|
"lure.sh/lure/internal/types"
|
||||||
"go.arsenm.dev/lure/internal/shutils/decoder"
|
"lure.sh/lure/pkg/distro"
|
||||||
"go.arsenm.dev/lure/internal/types"
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
"go.arsenm.dev/lure/vercmp"
|
|
||||||
"mvdan.cc/sh/v3/expand"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
@ -32,8 +49,15 @@ import (
|
|||||||
|
|
||||||
// Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned
|
// Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned
|
||||||
// and its packages will be written to the DB. If it does exist, it will be pulled.
|
// and its packages will be written to the DB. If it does exist, it will be pulled.
|
||||||
// In this case, only changed packages will be processed.
|
// In this case, only changed packages will be processed if possible.
|
||||||
func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
// If repos is set to nil, the repos in the LURE config will be used.
|
||||||
|
func Pull(ctx context.Context, repos []types.Repo) error {
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
|
if repos == nil {
|
||||||
|
repos = config.Config(ctx).Repos
|
||||||
|
}
|
||||||
|
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
repoURL, err := url.Parse(repo.URL)
|
repoURL, err := url.Parse(repo.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -41,7 +65,7 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Pulling repository").Str("name", repo.Name).Send()
|
log.Info("Pulling repository").Str("name", repo.Name).Send()
|
||||||
repoDir := filepath.Join(config.RepoDir, repo.Name)
|
repoDir := filepath.Join(config.GetPaths(ctx).RepoDir, repo.Name)
|
||||||
|
|
||||||
var repoFS billy.Filesystem
|
var repoFS billy.Filesystem
|
||||||
gitDir := filepath.Join(repoDir, ".git")
|
gitDir := filepath.Join(repoDir, ".git")
|
||||||
@ -71,7 +95,7 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
|||||||
repoFS = w.Filesystem
|
repoFS = w.Filesystem
|
||||||
|
|
||||||
// Make sure the DB is created even if the repo is up to date
|
// Make sure the DB is created even if the repo is up to date
|
||||||
if !errors.Is(err, git.NoErrAlreadyUpToDate) || !config.DBPresent {
|
if !errors.Is(err, git.NoErrAlreadyUpToDate) || db.IsEmpty(ctx) {
|
||||||
new, err := r.Head()
|
new, err := r.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -80,13 +104,13 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
|||||||
// If the DB was not present at startup, that means it's
|
// If the DB was not present at startup, that means it's
|
||||||
// empty. In this case, we need to update the DB fully
|
// empty. In this case, we need to update the DB fully
|
||||||
// rather than just incrementally.
|
// rather than just incrementally.
|
||||||
if config.DBPresent {
|
if db.IsEmpty(ctx) {
|
||||||
err = processRepoChanges(ctx, repo, r, w, old, new, gdb)
|
err = processRepoFull(ctx, repo, repoDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = processRepoFull(ctx, repo, repoDir, gdb)
|
err = processRepoChanges(ctx, repo, r, w, old, new)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -103,19 +127,15 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(repoURL.Scheme, "git+") {
|
_, err = git.PlainCloneContext(ctx, repoDir, false, &git.CloneOptions{
|
||||||
repoURL.Scheme = "git+" + repoURL.Scheme
|
URL: repoURL.String(),
|
||||||
}
|
Progress: os.Stderr,
|
||||||
|
|
||||||
err = download.Get(ctx, download.GetOptions{
|
|
||||||
SourceURL: repoURL.String(),
|
|
||||||
Destination: repoDir,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = processRepoFull(ctx, repo, repoDir, gdb)
|
err = processRepoFull(ctx, repo, repoDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -136,9 +156,13 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
|||||||
}
|
}
|
||||||
fl.Close()
|
fl.Close()
|
||||||
|
|
||||||
currentVer, _, _ := strings.Cut(config.Version, "-")
|
// If the version doesn't have a "v" prefix, it's not a standard version.
|
||||||
if vercmp.Compare(currentVer, repoCfg.Repo.MinVersion) == -1 {
|
// It may be "unknown" or a git version, but either way, there's no way
|
||||||
log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send()
|
// to compare it to the repo version, so only compare versions with the "v".
|
||||||
|
if strings.HasPrefix(config.Version, "v") {
|
||||||
|
if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 {
|
||||||
|
log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +181,7 @@ type action struct {
|
|||||||
File string
|
File string
|
||||||
}
|
}
|
||||||
|
|
||||||
func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference, gdb *sqlx.DB) error {
|
func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error {
|
||||||
oldCommit, err := r.CommitObject(old.Hash())
|
oldCommit, err := r.CommitObject(old.Hash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -219,11 +243,11 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
|
|||||||
env := append(os.Environ(), "scriptdir="+filepath.Dir(filepath.Join(repoDir, action.File)))
|
env := append(os.Environ(), "scriptdir="+filepath.Dir(filepath.Join(repoDir, action.File)))
|
||||||
runner, err := interp.New(
|
runner, err := interp.New(
|
||||||
interp.Env(expand.ListEnviron(env...)),
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
interp.ExecHandler(shutils.NopExec),
|
interp.ExecHandler(handlers.NopExec),
|
||||||
interp.ReadDirHandler(shutils.RestrictedReadDir(repoDir)),
|
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
|
||||||
interp.StatHandler(shutils.RestrictedStat(repoDir)),
|
interp.StatHandler(handlers.RestrictedStat(repoDir)),
|
||||||
interp.OpenHandler(shutils.RestrictedOpen(repoDir)),
|
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
|
||||||
interp.StdIO(shutils.NopRWC{}, shutils.NopRWC{}, shutils.NopRWC{}),
|
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -251,7 +275,7 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.DeletePkgs(gdb, "name = ? AND repository = ?", pkg.Name, repo.Name)
|
err = db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -271,6 +295,9 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
|
|||||||
}
|
}
|
||||||
|
|
||||||
pkg := db.Package{
|
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{}),
|
Depends: db.NewJSON(map[string][]string{}),
|
||||||
BuildDepends: db.NewJSON(map[string][]string{}),
|
BuildDepends: db.NewJSON(map[string][]string{}),
|
||||||
Repository: repo.Name,
|
Repository: repo.Name,
|
||||||
@ -283,7 +310,7 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
|
|||||||
|
|
||||||
resolveOverrides(runner, &pkg)
|
resolveOverrides(runner, &pkg)
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, pkg)
|
err = db.InsertPackage(ctx, pkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -309,7 +336,7 @@ func isValid(from, to diff.File) bool {
|
|||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
|
|
||||||
func processRepoFull(ctx context.Context, repo types.Repo, repoDir string, gdb *sqlx.DB) error {
|
func processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error {
|
||||||
glob := filepath.Join(repoDir, "/*/lure.sh")
|
glob := filepath.Join(repoDir, "/*/lure.sh")
|
||||||
matches, err := filepath.Glob(glob)
|
matches, err := filepath.Glob(glob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -322,11 +349,11 @@ func processRepoFull(ctx context.Context, repo types.Repo, repoDir string, gdb *
|
|||||||
env := append(os.Environ(), "scriptdir="+filepath.Dir(match))
|
env := append(os.Environ(), "scriptdir="+filepath.Dir(match))
|
||||||
runner, err := interp.New(
|
runner, err := interp.New(
|
||||||
interp.Env(expand.ListEnviron(env...)),
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
interp.ExecHandler(shutils.NopExec),
|
interp.ExecHandler(handlers.NopExec),
|
||||||
interp.ReadDirHandler(shutils.RestrictedReadDir(repoDir)),
|
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
|
||||||
interp.StatHandler(shutils.RestrictedStat(repoDir)),
|
interp.StatHandler(handlers.RestrictedStat(repoDir)),
|
||||||
interp.OpenHandler(shutils.RestrictedOpen(repoDir)),
|
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
|
||||||
interp.StdIO(shutils.NopRWC{}, shutils.NopRWC{}, shutils.NopRWC{}),
|
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -338,6 +365,9 @@ func processRepoFull(ctx context.Context, repo types.Repo, repoDir string, gdb *
|
|||||||
}
|
}
|
||||||
|
|
||||||
pkg := db.Package{
|
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{}),
|
Depends: db.NewJSON(map[string][]string{}),
|
||||||
BuildDepends: db.NewJSON(map[string][]string{}),
|
BuildDepends: db.NewJSON(map[string][]string{}),
|
||||||
Repository: repo.Name,
|
Repository: repo.Name,
|
||||||
@ -350,7 +380,7 @@ func processRepoFull(ctx context.Context, repo types.Repo, repoDir string, gdb *
|
|||||||
|
|
||||||
resolveOverrides(runner, &pkg)
|
resolveOverrides(runner, &pkg)
|
||||||
|
|
||||||
err = db.InsertPackage(gdb, pkg)
|
err = db.InsertPackage(ctx, pkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -378,20 +408,34 @@ func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runn
|
|||||||
return d.DecodeVars(pkg)
|
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) {
|
func resolveOverrides(runner *interp.Runner, pkg *db.Package) {
|
||||||
|
pkgVal := reflect.ValueOf(pkg).Elem()
|
||||||
for name, val := range runner.Vars {
|
for name, val := range runner.Vars {
|
||||||
if strings.HasPrefix(name, "deps") {
|
for prefix, field := range overridable {
|
||||||
override := strings.TrimPrefix(name, "deps")
|
if strings.HasPrefix(name, prefix) {
|
||||||
override = strings.TrimPrefix(override, "_")
|
override := strings.TrimPrefix(name, prefix)
|
||||||
|
override = strings.TrimPrefix(override, "_")
|
||||||
|
|
||||||
pkg.Depends.Val[override] = val.List
|
field := pkgVal.FieldByName(field)
|
||||||
} else if strings.HasPrefix(name, "build_deps") {
|
varVal := field.FieldByName("Val")
|
||||||
override := strings.TrimPrefix(name, "build_deps")
|
varType := varVal.Type()
|
||||||
override = strings.TrimPrefix(override, "_")
|
|
||||||
|
|
||||||
pkg.BuildDepends.Val[override] = val.List
|
switch varType.Elem().String() {
|
||||||
} else {
|
case "[]string":
|
||||||
continue
|
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.List))
|
||||||
|
case "string":
|
||||||
|
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.Str))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
109
pkg/repos/pull_test.go
Normal file
109
pkg/repos/pull_test.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2023 Elara 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 (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"lure.sh/lure/internal/config"
|
||||||
|
"lure.sh/lure/internal/db"
|
||||||
|
"lure.sh/lure/internal/types"
|
||||||
|
"lure.sh/lure/pkg/repos"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setCfgDirs(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
paths := config.GetPaths()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
paths.CacheDir, err = os.MkdirTemp("/tmp", "lure-pull-test.*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
|
||||||
|
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
|
||||||
|
|
||||||
|
err = os.MkdirAll(paths.RepoDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(paths.PkgsDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.DBPath = filepath.Join(paths.CacheDir, "db")
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeCacheDir(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
err := os.RemoveAll(config.GetPaths().CacheDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPull(t *testing.T) {
|
||||||
|
_, err := db.Open(":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
setCfgDirs(t)
|
||||||
|
defer removeCacheDir(t)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
err = repos.Pull(ctx, []types.Repo{
|
||||||
|
{
|
||||||
|
Name: "default",
|
||||||
|
URL: "https://github.com/Arsen6331/lure-repo.git",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := db.GetPkgs("name LIKE 'itd%'")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkgAmt int
|
||||||
|
for result.Next() {
|
||||||
|
var dbPkg db.Package
|
||||||
|
err = result.StructScan(&dbPkg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, got %s", err)
|
||||||
|
}
|
||||||
|
pkgAmt++
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkgAmt < 2 {
|
||||||
|
t.Errorf("Expected 2 packages to match, got %d", pkgAmt)
|
||||||
|
}
|
||||||
|
}
|
167
pkg/search/search.go
Normal file
167
pkg/search/search.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"lure.sh/lure/internal/config"
|
||||||
|
"lure.sh/lure/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter represents search filters.
|
||||||
|
type Filter int
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
const (
|
||||||
|
FilterNone Filter = iota
|
||||||
|
FilterInRepo
|
||||||
|
FilterSupportsArch
|
||||||
|
)
|
||||||
|
|
||||||
|
// SoryBy represents a value that packages can be sorted by.
|
||||||
|
type SortBy int
|
||||||
|
|
||||||
|
// Sort values
|
||||||
|
const (
|
||||||
|
SortByNone = iota
|
||||||
|
SortByName
|
||||||
|
SortByRepo
|
||||||
|
SortByVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
// Package represents a package from LURE's database
|
||||||
|
type Package struct {
|
||||||
|
Name string
|
||||||
|
Version string
|
||||||
|
Release int
|
||||||
|
Epoch uint
|
||||||
|
Description map[string]string
|
||||||
|
Homepage map[string]string
|
||||||
|
Maintainer map[string]string
|
||||||
|
Architectures []string
|
||||||
|
Licenses []string
|
||||||
|
Provides []string
|
||||||
|
Conflicts []string
|
||||||
|
Replaces []string
|
||||||
|
Depends map[string][]string
|
||||||
|
BuildDepends map[string][]string
|
||||||
|
OptDepends map[string][]string
|
||||||
|
Repository string
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertPkg(p db.Package) Package {
|
||||||
|
return Package{
|
||||||
|
Name: p.Name,
|
||||||
|
Version: p.Version,
|
||||||
|
Release: p.Release,
|
||||||
|
Epoch: p.Epoch,
|
||||||
|
Description: p.Description.Val,
|
||||||
|
Homepage: p.Homepage.Val,
|
||||||
|
Maintainer: p.Maintainer.Val,
|
||||||
|
Architectures: p.Architectures.Val,
|
||||||
|
Licenses: p.Licenses.Val,
|
||||||
|
Provides: p.Provides.Val,
|
||||||
|
Conflicts: p.Conflicts.Val,
|
||||||
|
Replaces: p.Replaces.Val,
|
||||||
|
Depends: p.Depends.Val,
|
||||||
|
BuildDepends: p.BuildDepends.Val,
|
||||||
|
OptDepends: p.OptDepends.Val,
|
||||||
|
Repository: p.Repository,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options contains the options for a search.
|
||||||
|
type Options struct {
|
||||||
|
Filter Filter
|
||||||
|
FilterValue string
|
||||||
|
SortBy SortBy
|
||||||
|
Limit int64
|
||||||
|
Query string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search searches for packages in the database based on the given options.
|
||||||
|
func Search(ctx context.Context, opts Options) ([]Package, error) {
|
||||||
|
query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
|
||||||
|
args := []any{"%" + opts.Query + "%", "%" + opts.Query + "%", opts.Query}
|
||||||
|
|
||||||
|
if opts.Filter != FilterNone {
|
||||||
|
switch opts.Filter {
|
||||||
|
case FilterInRepo:
|
||||||
|
query += " AND repository = ?"
|
||||||
|
case FilterSupportsArch:
|
||||||
|
query += " AND json_array_contains(architectures, ?)"
|
||||||
|
}
|
||||||
|
args = append(args, opts.FilterValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.SortBy != SortByNone {
|
||||||
|
switch opts.SortBy {
|
||||||
|
case SortByName:
|
||||||
|
query += " ORDER BY name"
|
||||||
|
case SortByRepo:
|
||||||
|
query += " ORDER BY repository"
|
||||||
|
case SortByVersion:
|
||||||
|
query += " ORDER BY version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Limit != 0 {
|
||||||
|
query += " LIMIT " + strconv.FormatInt(opts.Limit, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := db.GetPkgs(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []Package
|
||||||
|
for result.Next() {
|
||||||
|
pkg := db.Package{}
|
||||||
|
err = result.StructScan(&pkg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, convertPkg(pkg))
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPkg gets a single package from the database and returns it.
|
||||||
|
func GetPkg(ctx context.Context, repo, name string) (Package, error) {
|
||||||
|
pkg, err := db.GetPkg(ctx, "name = ? AND repository = ?", name, repo)
|
||||||
|
return convertPkg(*pkg), err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidArgument is an error returned by GetScript when one of its arguments
|
||||||
|
// contain invalid characters
|
||||||
|
ErrInvalidArgument = errors.New("name and repository must not contain . or /")
|
||||||
|
|
||||||
|
// ErrScriptNotFound is returned by GetScript if it can't find the script requested
|
||||||
|
// by the user.
|
||||||
|
ErrScriptNotFound = errors.New("requested script not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetScript returns a reader containing the build script for a given package.
|
||||||
|
func GetScript(ctx context.Context, repo, name string) (io.ReadCloser, error) {
|
||||||
|
if strings.Contains(name, "./") || strings.ContainsAny(repo, "./") {
|
||||||
|
return nil, ErrInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, repo, name, "lure.sh")
|
||||||
|
fl, err := os.Open(scriptPath)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil, ErrScriptNotFound
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fl, nil
|
||||||
|
}
|
198
repo.go
198
repo.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -24,91 +24,139 @@ import (
|
|||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.arsenm.dev/logger/log"
|
"lure.sh/lure/internal/config"
|
||||||
"go.arsenm.dev/lure/internal/config"
|
"lure.sh/lure/internal/db"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/internal/types"
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
"go.arsenm.dev/lure/internal/types"
|
"lure.sh/lure/pkg/repos"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addrepoCmd(c *cli.Context) error {
|
var addrepoCmd = &cli.Command{
|
||||||
name := c.String("name")
|
Name: "addrepo",
|
||||||
repoURL := c.String("url")
|
Usage: "Add a new repository",
|
||||||
|
Aliases: []string{"ar"},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Name of the new repo",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "url",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "URL of the new repo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
for _, repo := range cfg.Repos {
|
name := c.String("name")
|
||||||
if repo.URL == repoURL {
|
repoURL := c.String("url")
|
||||||
log.Fatal("Repo already exists").Str("name", repo.Name).Send()
|
|
||||||
|
cfg := config.Config(ctx)
|
||||||
|
|
||||||
|
for _, repo := range cfg.Repos {
|
||||||
|
if repo.URL == repoURL {
|
||||||
|
log.Fatal("Repo already exists").Str("name", repo.Name).Send()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cfg.Repos = append(cfg.Repos, types.Repo{
|
cfg.Repos = append(cfg.Repos, types.Repo{
|
||||||
Name: name,
|
Name: name,
|
||||||
URL: repoURL,
|
URL: repoURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
cfgFl, err := os.Create(config.ConfigPath)
|
cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error opening config file").Err(err).Send()
|
log.Fatal("Error opening config file").Err(err).Send()
|
||||||
}
|
|
||||||
|
|
||||||
err = toml.NewEncoder(cfgFl).Encode(&cfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error encoding config").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = repos.Pull(c.Context, gdb, cfg.Repos)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error pulling repos").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func removerepoCmd(c *cli.Context) error {
|
|
||||||
name := c.String("name")
|
|
||||||
|
|
||||||
found := false
|
|
||||||
index := 0
|
|
||||||
for i, repo := range cfg.Repos {
|
|
||||||
if repo.Name == name {
|
|
||||||
index = i
|
|
||||||
found = true
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
log.Fatal("Repo does not exist").Str("name", name).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.Repos = slices.Delete(cfg.Repos, index, index+1)
|
err = toml.NewEncoder(cfgFl).Encode(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error encoding config").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
cfgFl, err := os.Create(config.ConfigPath)
|
err = repos.Pull(ctx, cfg.Repos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error opening config file").Err(err).Send()
|
log.Fatal("Error pulling repos").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = toml.NewEncoder(cfgFl).Encode(&cfg)
|
return nil
|
||||||
if err != nil {
|
},
|
||||||
log.Fatal("Error encoding config").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.RemoveAll(filepath.Join(config.RepoDir, name))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error removing repo directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.DeletePkgs(gdb, "repository = ?", name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error removing packages from database").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshCmd(c *cli.Context) error {
|
var removerepoCmd = &cli.Command{
|
||||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
Name: "removerepo",
|
||||||
if err != nil {
|
Usage: "Remove an existing repository",
|
||||||
log.Fatal("Error pulling repos").Err(err).Send()
|
Aliases: []string{"rr"},
|
||||||
}
|
Flags: []cli.Flag{
|
||||||
return nil
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Name of the repo to be deleted",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
|
name := c.String("name")
|
||||||
|
cfg := config.Config(ctx)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
index := 0
|
||||||
|
for i, repo := range cfg.Repos {
|
||||||
|
if repo.Name == name {
|
||||||
|
index = i
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
log.Fatal("Repo does not exist").Str("name", name).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Repos = slices.Delete(cfg.Repos, index, index+1)
|
||||||
|
|
||||||
|
cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error opening config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(cfgFl).Encode(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error encoding config").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(filepath.Join(config.GetPaths(ctx).RepoDir, name))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error removing repo directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.DeletePkgs(ctx, "repository = ?", name)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error removing packages from database").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var refreshCmd = &cli.Command{
|
||||||
|
Name: "refresh",
|
||||||
|
Usage: "Pull all repositories that have changed",
|
||||||
|
Aliases: []string{"ref"},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error pulling repos").Err(err).Send()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
git describe --tags > internal/config/version.txt
|
|
@ -1,5 +1,21 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LURE - Linux User REpository
|
||||||
|
# Copyright (C) 2023 Elara 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/>.
|
||||||
|
|
||||||
info() {
|
info() {
|
||||||
echo $'\x1b[32m[INFO]\x1b[0m' $@
|
echo $'\x1b[32m[INFO]\x1b[0m' $@
|
||||||
}
|
}
|
||||||
@ -26,6 +42,7 @@ installPkg() {
|
|||||||
case $1 in
|
case $1 in
|
||||||
pacman) $rootCmd pacman --noconfirm -U ${@:2} ;;
|
pacman) $rootCmd pacman --noconfirm -U ${@:2} ;;
|
||||||
apk) $rootCmd apk add --allow-untrusted ${@:2} ;;
|
apk) $rootCmd apk add --allow-untrusted ${@:2} ;;
|
||||||
|
zypper) $rootCmd zypper --no-gpg-checks install ${@:2} ;;
|
||||||
*) $rootCmd $1 install -y ${@:2} ;;
|
*) $rootCmd $1 install -y ${@:2} ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@ -64,11 +81,11 @@ else
|
|||||||
error "No supported package manager detected!"
|
error "No supported package manager detected!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
latestVersion=$(curl -sI 'https://gitea.arsenm.dev/Arsen6331/lure/releases/latest' | grep -o 'location: .*' | rev | cut -d '/' -f1 | rev | tr -d '[:space:]')
|
latestVersion=$(curl -sI 'https://gitea.elara.ws/lure/lure/releases/latest' | grep -io 'location: .*' | rev | cut -d '/' -f1 | rev | tr -d '[:space:]')
|
||||||
info "Found latest LURE version:" $latestVersion
|
info "Found latest LURE version:" $latestVersion
|
||||||
|
|
||||||
fname="$(mktemp -u -p /tmp "lure.XXXXXXXXXX").${pkgFormat}"
|
fname="$(mktemp -u -p /tmp "lure.XXXXXXXXXX").${pkgFormat}"
|
||||||
url="https://gitea.arsenm.dev/Arsen6331/lure/releases/download/${latestVersion}/linux-user-repository-${latestVersion#v}-linux-$(uname -m).${pkgFormat}"
|
url="https://gitea.elara.ws/lure/lure/releases/download/${latestVersion}/linux-user-repository-${latestVersion#v}-linux-$(uname -m).${pkgFormat}"
|
||||||
|
|
||||||
info "Downloading LURE package"
|
info "Downloading LURE package"
|
||||||
curl -L $url -o $fname
|
curl -L $url -o $fname
|
||||||
|
90
upgrade.go
90
upgrade.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2022 Arsen Musayelyan
|
* Copyright (C) 2023 Elara Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -23,44 +23,66 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.arsenm.dev/logger/log"
|
"lure.sh/lure/internal/config"
|
||||||
"go.arsenm.dev/lure/distro"
|
"lure.sh/lure/internal/db"
|
||||||
"go.arsenm.dev/lure/internal/db"
|
"lure.sh/lure/internal/types"
|
||||||
"go.arsenm.dev/lure/internal/repos"
|
"lure.sh/lure/pkg/build"
|
||||||
"go.arsenm.dev/lure/manager"
|
"lure.sh/lure/pkg/distro"
|
||||||
"go.arsenm.dev/lure/vercmp"
|
"lure.sh/lure/pkg/loggerctx"
|
||||||
|
"lure.sh/lure/pkg/manager"
|
||||||
|
"lure.sh/lure/pkg/repos"
|
||||||
|
"go.elara.ws/vercmp"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func upgradeCmd(c *cli.Context) error {
|
var upgradeCmd = &cli.Command{
|
||||||
info, err := distro.ParseOSRelease(c.Context)
|
Name: "upgrade",
|
||||||
if err != nil {
|
Usage: "Upgrade all installed packages",
|
||||||
log.Fatal("Error parsing os-release file").Err(err).Send()
|
Aliases: []string{"up"},
|
||||||
}
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "clean",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Build package from scratch even if there's an already built package available",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
ctx := c.Context
|
||||||
|
log := loggerctx.From(ctx)
|
||||||
|
|
||||||
mgr := manager.Detect()
|
info, err := distro.ParseOSRelease(ctx)
|
||||||
if mgr == nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repos.Pull(c.Context, gdb, cfg.Repos)
|
mgr := manager.Detect()
|
||||||
if err != nil {
|
if mgr == nil {
|
||||||
log.Fatal("Error pulling repos").Err(err).Send()
|
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
updates, err := checkForUpdates(c.Context, mgr, info)
|
err = repos.Pull(ctx, config.Config(ctx).Repos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error checking for updates").Err(err).Send()
|
log.Fatal("Error pulling repos").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(updates) > 0 {
|
updates, err := checkForUpdates(ctx, mgr, info)
|
||||||
installPkgs(c.Context, updates, nil, mgr)
|
if err != nil {
|
||||||
} else {
|
log.Fatal("Error checking for updates").Err(err).Send()
|
||||||
log.Info("There is nothing to do.").Send()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
if len(updates) > 0 {
|
||||||
|
build.InstallPkgs(ctx, updates, nil, types.BuildOpts{
|
||||||
|
Manager: mgr,
|
||||||
|
Clean: c.Bool("clean"),
|
||||||
|
Interactive: c.Bool("interactive"),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
log.Info("There is nothing to do.").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) {
|
func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) {
|
||||||
@ -70,21 +92,21 @@ func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
pkgNames := maps.Keys(installed)
|
pkgNames := maps.Keys(installed)
|
||||||
found, _, err := repos.FindPkgs(gdb, pkgNames)
|
found, _, err := repos.FindPkgs(ctx, pkgNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var out []db.Package
|
var out []db.Package
|
||||||
for pkgName, pkgs := range found {
|
for pkgName, pkgs := range found {
|
||||||
if slices.Contains(cfg.IgnorePkgUpdates, pkgName) {
|
if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkgName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pkgs) > 1 {
|
if len(pkgs) > 1 {
|
||||||
// Puts the element with the highest version first
|
// Puts the element with the highest version first
|
||||||
slices.SortFunc(pkgs, func(a, b db.Package) bool {
|
slices.SortFunc(pkgs, func(a, b db.Package) int {
|
||||||
return vercmp.Compare(a.Version, b.Version) == 1
|
return vercmp.Compare(a.Version, b.Version)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
165
vercmp/vercmp.go
165
vercmp/vercmp.go
@ -1,165 +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 vercmp
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compare compares two version strings.
|
|
||||||
// It returns 1 if v1 is greater,
|
|
||||||
// 0 if the versions are equal,
|
|
||||||
// and -1 if v2 is greater
|
|
||||||
func Compare(v1, v2 string) int {
|
|
||||||
if v1 == v2 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return sepVerCmp(sepLabel(v1), sepLabel(v2))
|
|
||||||
}
|
|
||||||
|
|
||||||
func sepVerCmp(e1, e2 []string) int {
|
|
||||||
if slices.Equal(e1, e2) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// proc stores the amount of elements processed
|
|
||||||
proc := 0
|
|
||||||
|
|
||||||
for i := 0; i < len(e1); i++ {
|
|
||||||
proc++
|
|
||||||
|
|
||||||
if i >= len(e2) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
elem1 := e1[i]
|
|
||||||
elem2 := e2[i]
|
|
||||||
|
|
||||||
if elem1 == elem2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if isNumElem(elem1) && isNumElem(elem2) {
|
|
||||||
elem1v, err := strconv.ParseInt(elem1, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
// error should never happen due to isNumElem()
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
elem2v, err := strconv.ParseInt(elem2, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
// error should never happen due to isNumElem()
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if elem1v > elem2v {
|
|
||||||
return 1
|
|
||||||
} else if elem1v < elem2v {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
} else if isNumElem(elem1) && isAlphaElem(elem2) {
|
|
||||||
return 1
|
|
||||||
} else if isAlphaElem(elem1) && isNumElem(elem2) {
|
|
||||||
return -1
|
|
||||||
} else if isAlphaElem(elem1) && isAlphaElem(elem2) {
|
|
||||||
if elem1 > elem2 {
|
|
||||||
return 1
|
|
||||||
} else if elem1 < elem2 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if proc < len(e2) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func sepLabel(label string) []string {
|
|
||||||
const (
|
|
||||||
other = iota
|
|
||||||
alpha
|
|
||||||
num
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
curType uint8
|
|
||||||
out []string
|
|
||||||
sb strings.Builder
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, char := range label {
|
|
||||||
if isNum(char) {
|
|
||||||
if curType != num && curType != other {
|
|
||||||
out = append(out, sb.String())
|
|
||||||
sb.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.WriteRune(char)
|
|
||||||
curType = num
|
|
||||||
} else if isAlpha(char) {
|
|
||||||
if curType != alpha && curType != other {
|
|
||||||
out = append(out, sb.String())
|
|
||||||
sb.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.WriteRune(char)
|
|
||||||
curType = alpha
|
|
||||||
} else {
|
|
||||||
if curType != other {
|
|
||||||
out = append(out, sb.String())
|
|
||||||
sb.Reset()
|
|
||||||
}
|
|
||||||
curType = other
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sb.Len() != 0 {
|
|
||||||
out = append(out, sb.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNumElem(s string) bool {
|
|
||||||
// Check only the first rune as all elements
|
|
||||||
// should consist of the same type of rune
|
|
||||||
return isNum([]rune(s[:1])[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNum(r rune) bool {
|
|
||||||
return r >= '0' && r <= '9'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAlphaElem(s string) bool {
|
|
||||||
// Check only the first rune as all elements
|
|
||||||
// should consist of the same type of rune
|
|
||||||
return isAlpha([]rune(s[:1])[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAlpha(r rune) bool {
|
|
||||||
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
|
|
||||||
}
|
|
@ -1,82 +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 vercmp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSepLabel(t *testing.T) {
|
|
||||||
type item struct {
|
|
||||||
label string
|
|
||||||
expected []string
|
|
||||||
}
|
|
||||||
|
|
||||||
table := []item{
|
|
||||||
{"2.0.1", []string{"2", "0", "1"}},
|
|
||||||
{"v0.0.1", []string{"v", "0", "0", "1"}},
|
|
||||||
{"2xFg33.+f.5", []string{"2", "xFg", "33", "f", "5"}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, it := range table {
|
|
||||||
t.Run(it.label, func(t *testing.T) {
|
|
||||||
s := sepLabel(it.label)
|
|
||||||
if !slices.Equal(s, it.expected) {
|
|
||||||
t.Errorf("Expected %v, got %v", it.expected, s)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerCmp(t *testing.T) {
|
|
||||||
type item struct {
|
|
||||||
v1, v2 string
|
|
||||||
expected int
|
|
||||||
}
|
|
||||||
|
|
||||||
table := []item{
|
|
||||||
{"1.0010", "1.9", 1},
|
|
||||||
{"1.05", "1.5", 0},
|
|
||||||
{"1.0", "1", 1},
|
|
||||||
{"1", "1.0", -1},
|
|
||||||
{"2.50", "2.5", 1},
|
|
||||||
{"FC5", "fc4", -1},
|
|
||||||
{"2a", "2.0", -1},
|
|
||||||
{"1.0", "1.fc4", 1},
|
|
||||||
{"3.0.0_fc", "3.0.0.fc", 0},
|
|
||||||
{"4.1__", "4.1+", 0},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, it := range table {
|
|
||||||
t.Run(it.v1+"/"+it.v2, func(t *testing.T) {
|
|
||||||
c := Compare(it.v1, it.v2)
|
|
||||||
if c != it.expected {
|
|
||||||
t.Errorf("Expected %d, got %d", it.expected, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure opposite comparison gives opposite value
|
|
||||||
c = -Compare(it.v2, it.v1)
|
|
||||||
if c != it.expected {
|
|
||||||
t.Errorf("Expected %d, got %d (opposite)", it.expected, c)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user