mirror of
https://github.com/gogrlx/snack.git
synced 2026-04-02 05:08:42 -07:00
Merge pull request #2 from gogrlx/cd/apk
feat(apk): implement apk-tools package manager wrapper
This commit is contained in:
73
apk/apk.go
73
apk/apk.go
@@ -1,2 +1,75 @@
|
|||||||
// Package apk provides Go bindings for apk-tools (Alpine Linux package manager).
|
// Package apk provides Go bindings for apk-tools (Alpine Linux package manager).
|
||||||
package apk
|
package apk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/gogrlx/snack"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Apk wraps apk-tools operations.
|
||||||
|
type Apk struct{}
|
||||||
|
|
||||||
|
// New returns a new Apk manager.
|
||||||
|
func New() *Apk {
|
||||||
|
return &Apk{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compile-time check
|
||||||
|
var _ snack.Manager = (*Apk)(nil)
|
||||||
|
|
||||||
|
// Name returns "apk".
|
||||||
|
func (a *Apk) Name() string { return "apk" }
|
||||||
|
|
||||||
|
// Available reports whether apk is present on the system.
|
||||||
|
func (a *Apk) Available() bool { return available() }
|
||||||
|
|
||||||
|
// Install one or more packages.
|
||||||
|
func (a *Apk) Install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
||||||
|
return install(ctx, pkgs, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove one or more packages.
|
||||||
|
func (a *Apk) Remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
||||||
|
return remove(ctx, pkgs, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge removes packages including config files.
|
||||||
|
func (a *Apk) Purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
||||||
|
return purge(ctx, pkgs, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade all installed packages.
|
||||||
|
func (a *Apk) Upgrade(ctx context.Context, opts ...snack.Option) error {
|
||||||
|
return upgrade(ctx, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update refreshes the package index.
|
||||||
|
func (a *Apk) Update(ctx context.Context) error {
|
||||||
|
return update(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns all installed packages.
|
||||||
|
func (a *Apk) List(ctx context.Context) ([]snack.Package, error) {
|
||||||
|
return list(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search queries the index for matching packages.
|
||||||
|
func (a *Apk) Search(ctx context.Context, query string) ([]snack.Package, error) {
|
||||||
|
return search(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info returns details about a package.
|
||||||
|
func (a *Apk) Info(ctx context.Context, pkg string) (*snack.Package, error) {
|
||||||
|
return info(ctx, pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInstalled reports whether a package is installed.
|
||||||
|
func (a *Apk) IsInstalled(ctx context.Context, pkg string) (bool, error) {
|
||||||
|
return isInstalled(ctx, pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the installed version of a package.
|
||||||
|
func (a *Apk) Version(ctx context.Context, pkg string) (string, error) {
|
||||||
|
return version(ctx, pkg)
|
||||||
|
}
|
||||||
|
|||||||
147
apk/apk_linux.go
Normal file
147
apk/apk_linux.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package apk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gogrlx/snack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func available() bool {
|
||||||
|
_, err := exec.LookPath("apk")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildArgs(base []string, opts snack.Options) (string, []string) {
|
||||||
|
cmd := "apk"
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
if opts.Root != "" {
|
||||||
|
args = append(args, "--root", opts.Root)
|
||||||
|
}
|
||||||
|
if opts.DryRun {
|
||||||
|
args = append(args, "--simulate")
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, base...)
|
||||||
|
|
||||||
|
if opts.Sudo {
|
||||||
|
args = append([]string{cmd}, args...)
|
||||||
|
cmd = "sudo"
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd, args
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(ctx context.Context, base []string, opts ...snack.Option) (string, error) {
|
||||||
|
o := snack.ApplyOptions(opts...)
|
||||||
|
cmd, args := buildArgs(base, o)
|
||||||
|
c := exec.CommandContext(ctx, cmd, args...)
|
||||||
|
out, err := c.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
outStr := strings.TrimSpace(string(out))
|
||||||
|
if strings.Contains(outStr, "permission denied") || strings.Contains(outStr, "Permission denied") {
|
||||||
|
return outStr, fmt.Errorf("%s: %w", outStr, snack.ErrPermissionDenied)
|
||||||
|
}
|
||||||
|
return outStr, fmt.Errorf("apk: %s: %w", outStr, err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
||||||
|
args := append([]string{"add"}, pkgs...)
|
||||||
|
_, err := run(ctx, args, opts...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
||||||
|
args := append([]string{"del"}, pkgs...)
|
||||||
|
_, err := run(ctx, args, opts...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
||||||
|
args := append([]string{"del", "--purge"}, pkgs...)
|
||||||
|
_, err := run(ctx, args, opts...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgrade(ctx context.Context, opts ...snack.Option) error {
|
||||||
|
_, err := run(ctx, []string{"upgrade"}, opts...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(ctx context.Context) error {
|
||||||
|
_, err := run(ctx, []string{"update"})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(ctx context.Context) ([]snack.Package, error) {
|
||||||
|
cmd := exec.CommandContext(ctx, "apk", "list", "--installed")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("apk list: %w", err)
|
||||||
|
}
|
||||||
|
return parseListInstalled(string(out)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func search(ctx context.Context, query string) ([]snack.Package, error) {
|
||||||
|
cmd := exec.CommandContext(ctx, "apk", "search", "-v", query)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("apk search: %w", err)
|
||||||
|
}
|
||||||
|
results := parseSearch(string(out))
|
||||||
|
if len(results) == 0 {
|
||||||
|
return nil, fmt.Errorf("apk search %q: %w", query, snack.ErrNotFound)
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func info(ctx context.Context, pkg string) (*snack.Package, error) {
|
||||||
|
cmd := exec.CommandContext(ctx, "apk", "info", "-a", pkg)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("apk info %q: %w", pkg, snack.ErrNotFound)
|
||||||
|
}
|
||||||
|
p := parseInfo(string(out))
|
||||||
|
if p == nil {
|
||||||
|
return nil, fmt.Errorf("apk info %q: %w", pkg, snack.ErrNotFound)
|
||||||
|
}
|
||||||
|
name, ver := parseInfoNameVersion(string(out))
|
||||||
|
if name == "" {
|
||||||
|
name = pkg
|
||||||
|
}
|
||||||
|
p.Name = name
|
||||||
|
p.Version = ver
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInstalled(ctx context.Context, pkg string) (bool, error) {
|
||||||
|
cmd := exec.CommandContext(ctx, "apk", "info", "-e", pkg)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("apk info -e %q: %w", pkg, err)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func version(ctx context.Context, pkg string) (string, error) {
|
||||||
|
cmd := exec.CommandContext(ctx, "apk", "info", pkg)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("apk info %q: %w", pkg, snack.ErrNotInstalled)
|
||||||
|
}
|
||||||
|
_, ver := parseInfoNameVersion(string(out))
|
||||||
|
if ver == "" {
|
||||||
|
return "", fmt.Errorf("apk version %q: %w", pkg, snack.ErrNotInstalled)
|
||||||
|
}
|
||||||
|
return ver, nil
|
||||||
|
}
|
||||||
51
apk/apk_other.go
Normal file
51
apk/apk_other.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package apk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/gogrlx/snack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func available() bool { return false }
|
||||||
|
|
||||||
|
func install(_ context.Context, _ []string, _ ...snack.Option) error {
|
||||||
|
return snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(_ context.Context, _ []string, _ ...snack.Option) error {
|
||||||
|
return snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
func purge(_ context.Context, _ []string, _ ...snack.Option) error {
|
||||||
|
return snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgrade(_ context.Context, _ ...snack.Option) error {
|
||||||
|
return snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(_ context.Context) error {
|
||||||
|
return snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(_ context.Context) ([]snack.Package, error) {
|
||||||
|
return nil, snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
func search(_ context.Context, _ string) ([]snack.Package, error) {
|
||||||
|
return nil, snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
func info(_ context.Context, _ string) (*snack.Package, error) {
|
||||||
|
return nil, snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInstalled(_ context.Context, _ string) (bool, error) {
|
||||||
|
return false, snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
func version(_ context.Context, _ string) (string, error) {
|
||||||
|
return "", snack.ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
114
apk/apk_test.go
Normal file
114
apk/apk_test.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package apk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gogrlx/snack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSplitNameVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
}{
|
||||||
|
{"curl-8.5.0-r0", "curl", "8.5.0-r0"},
|
||||||
|
{"musl-1.2.4-r2", "musl", "1.2.4-r2"},
|
||||||
|
{"libcurl-doc-8.5.0-r0", "libcurl-doc", "8.5.0-r0"},
|
||||||
|
{"go-1.21.5-r0", "go", "1.21.5-r0"},
|
||||||
|
{"noversion", "noversion", ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
name, ver := splitNameVersion(tt.input)
|
||||||
|
if name != tt.name || ver != tt.version {
|
||||||
|
t.Errorf("splitNameVersion(%q) = (%q, %q), want (%q, %q)",
|
||||||
|
tt.input, name, ver, tt.name, tt.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseListInstalled(t *testing.T) {
|
||||||
|
output := `curl-8.5.0-r0 x86_64 {curl} (MIT) [installed]
|
||||||
|
musl-1.2.4-r2 x86_64 {musl} (MIT) [installed]
|
||||||
|
`
|
||||||
|
pkgs := parseListInstalled(output)
|
||||||
|
if len(pkgs) != 2 {
|
||||||
|
t.Fatalf("expected 2 packages, got %d", len(pkgs))
|
||||||
|
}
|
||||||
|
if pkgs[0].Name != "curl" || pkgs[0].Version != "8.5.0-r0" {
|
||||||
|
t.Errorf("unexpected first package: %+v", pkgs[0])
|
||||||
|
}
|
||||||
|
if !pkgs[0].Installed {
|
||||||
|
t.Error("expected Installed=true")
|
||||||
|
}
|
||||||
|
if pkgs[0].Arch != "x86_64" {
|
||||||
|
t.Errorf("expected arch x86_64, got %q", pkgs[0].Arch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSearch(t *testing.T) {
|
||||||
|
// verbose output
|
||||||
|
output := `curl-8.5.0-r0 - URL retrieval utility and library
|
||||||
|
curl-doc-8.5.0-r0 - URL retrieval utility and library (documentation)
|
||||||
|
`
|
||||||
|
pkgs := parseSearch(output)
|
||||||
|
if len(pkgs) != 2 {
|
||||||
|
t.Fatalf("expected 2 packages, got %d", len(pkgs))
|
||||||
|
}
|
||||||
|
if pkgs[0].Name != "curl" || pkgs[0].Version != "8.5.0-r0" {
|
||||||
|
t.Errorf("unexpected package: %+v", pkgs[0])
|
||||||
|
}
|
||||||
|
if pkgs[0].Description != "URL retrieval utility and library" {
|
||||||
|
t.Errorf("unexpected description: %q", pkgs[0].Description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSearchPlain(t *testing.T) {
|
||||||
|
output := `curl-8.5.0-r0
|
||||||
|
curl-doc-8.5.0-r0
|
||||||
|
`
|
||||||
|
pkgs := parseSearch(output)
|
||||||
|
if len(pkgs) != 2 {
|
||||||
|
t.Fatalf("expected 2 packages, got %d", len(pkgs))
|
||||||
|
}
|
||||||
|
if pkgs[0].Name != "curl" {
|
||||||
|
t.Errorf("expected curl, got %q", pkgs[0].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInfo(t *testing.T) {
|
||||||
|
output := `curl-8.5.0-r0 installed size:
|
||||||
|
description: URL retrieval utility and library
|
||||||
|
arch: x86_64
|
||||||
|
webpage: https://curl.se/
|
||||||
|
`
|
||||||
|
pkg := parseInfo(output)
|
||||||
|
if pkg == nil {
|
||||||
|
t.Fatal("expected non-nil package")
|
||||||
|
}
|
||||||
|
if pkg.Description != "URL retrieval utility and library" {
|
||||||
|
t.Errorf("unexpected description: %q", pkg.Description)
|
||||||
|
}
|
||||||
|
if pkg.Arch != "x86_64" {
|
||||||
|
t.Errorf("unexpected arch: %q", pkg.Arch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInfoNameVersion(t *testing.T) {
|
||||||
|
output := "curl-8.5.0-r0 description:\nsome stuff"
|
||||||
|
name, ver := parseInfoNameVersion(output)
|
||||||
|
if name != "curl" || ver != "8.5.0-r0" {
|
||||||
|
t.Errorf("got (%q, %q), want (curl, 8.5.0-r0)", name, ver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewImplementsManager(t *testing.T) {
|
||||||
|
var _ snack.Manager = New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestName(t *testing.T) {
|
||||||
|
a := New()
|
||||||
|
if a.Name() != "apk" {
|
||||||
|
t.Errorf("expected apk, got %q", a.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
136
apk/parse.go
Normal file
136
apk/parse.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package apk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gogrlx/snack"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseListInstalled parses output from `apk list --installed`.
|
||||||
|
// Each line looks like: "name-1.2.3-r0 x86_64 {origin} (license) [installed]"
|
||||||
|
func parseListInstalled(output string) []snack.Package {
|
||||||
|
var pkgs []snack.Package
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkg := parseListLine(line)
|
||||||
|
if pkg.Name != "" {
|
||||||
|
pkgs = append(pkgs, pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseListLine parses a single line from `apk list`.
|
||||||
|
// Format: "name-1.2.3-r0 x86_64 {origin} (license) [installed]"
|
||||||
|
func parseListLine(line string) snack.Package {
|
||||||
|
var pkg snack.Package
|
||||||
|
pkg.Installed = strings.Contains(line, "[installed]")
|
||||||
|
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) < 1 {
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
// First field is name-version
|
||||||
|
nameVer := fields[0]
|
||||||
|
pkg.Name, pkg.Version = splitNameVersion(nameVer)
|
||||||
|
|
||||||
|
if len(fields) >= 2 {
|
||||||
|
pkg.Arch = fields[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitNameVersion splits "name-1.2.3-r0" into ("name", "1.2.3-r0").
|
||||||
|
// apk versions start with a digit, so we find the last hyphen before a digit.
|
||||||
|
func splitNameVersion(s string) (string, string) {
|
||||||
|
for i := len(s) - 1; i > 0; i-- {
|
||||||
|
if s[i] == '-' && i+1 < len(s) && s[i+1] >= '0' && s[i+1] <= '9' {
|
||||||
|
return s[:i], s[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSearch parses output from `apk search` or `apk search -v`.
|
||||||
|
func parseSearch(output string) []snack.Package {
|
||||||
|
var pkgs []snack.Package
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// `apk search -v` output: "name-version - description"
|
||||||
|
if idx := strings.Index(line, " - "); idx != -1 {
|
||||||
|
nameVer := line[:idx]
|
||||||
|
desc := line[idx+3:]
|
||||||
|
name, ver := splitNameVersion(nameVer)
|
||||||
|
pkgs = append(pkgs, snack.Package{
|
||||||
|
Name: name,
|
||||||
|
Version: ver,
|
||||||
|
Description: desc,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// plain `apk search` just returns name-version
|
||||||
|
name, ver := splitNameVersion(line)
|
||||||
|
pkgs = append(pkgs, snack.Package{
|
||||||
|
Name: name,
|
||||||
|
Version: ver,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseInfo parses output from `apk info -a <pkg>`.
|
||||||
|
func parseInfo(output string) *snack.Package {
|
||||||
|
pkg := &snack.Package{}
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "description:") {
|
||||||
|
pkg.Description = strings.TrimSpace(strings.TrimPrefix(line, "description:"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First line is typically "pkgname-version description"
|
||||||
|
// But `apk info -a` starts with "pkgname-version installed size:"
|
||||||
|
// Let's parse key-value style
|
||||||
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if k, v, ok := strings.Cut(line, ":"); ok {
|
||||||
|
k = strings.TrimSpace(k)
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
switch strings.ToLower(k) {
|
||||||
|
case "description":
|
||||||
|
pkg.Description = v
|
||||||
|
case "arch":
|
||||||
|
pkg.Arch = v
|
||||||
|
case "url", "webpage":
|
||||||
|
// skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseInfoNameVersion extracts name and version from `apk info <pkg>` output.
|
||||||
|
// The first line is typically "pkgname-version description".
|
||||||
|
func parseInfoNameVersion(output string) (string, string) {
|
||||||
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
// first line: name-version
|
||||||
|
first := strings.Fields(lines[0])[0]
|
||||||
|
return splitNameVersion(first)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user