Browse Source

Added package parser and tests

v0
maze 3 years ago
parent
commit
87710d5edd
13 changed files with 595 additions and 48 deletions
  1. +2
    -3
      aur/client.go
  2. +2
    -15
      aur/response.go
  3. +271
    -28
      package.go
  4. +232
    -0
      package_test.go
  5. +11
    -2
      tarball/register.go
  6. BIN
      tarball/testdata/testdata.tar
  7. BIN
      tarball/testdata/testdata.tar.Z
  8. BIN
      tarball/testdata/testdata.tar.bz2
  9. BIN
      tarball/testdata/testdata.tar.gz
  10. BIN
      tarball/testdata/testdata.tar.lzo
  11. BIN
      tarball/testdata/testdata.tar.xz
  12. +1
    -0
      tarball/testdata/testdata.tar.zz
  13. +76
    -0
      type.go

+ 2
- 3
aur/client.go View File

@ -145,8 +145,8 @@ type Info struct {
NumVotes int
Popularity float64
Maintainer string
FirstSubmitted Time
LastModified Time
FirstSubmitted archlinux.Timestamp
LastModified archlinux.Timestamp
URLPath string
Keywords []string
}
@ -158,6 +158,5 @@ func fixup(results []*Info) {
if i := strings.IndexByte(r.Version, '-'); i != -1 {
r.Version, r.Release = r.Version[:i], r.Version[i+1:]
}
r.URL, _ = url.Parse(fmt.Sprintf("%s/packages/%s", DefaultURL, url.QueryEscape(r.Name)))
}
}

+ 2
- 15
aur/response.go View File

@ -1,10 +1,8 @@
package aur
import (
"encoding/json"
"time"
)
import "encoding/json"
// Response is a response from the RPC API
type Response struct {
Version int `json:"version"`
Type string `json:"type"`
@ -16,14 +14,3 @@ type Response struct {
func (res Response) Error() string {
return res.ErrorMessage
}
type Time time.Time
func (t *Time) UnmarshalJSON(b []byte) error {
var i int64
if err := json.Unmarshal(b, &i); err != nil {
return err
}
*t = Time(time.Unix(i, 0))
return nil
}

+ 271
- 28
package.go View File

@ -1,60 +1,303 @@
package archlinux
import (
"bufio"
"errors"
"fmt"
"io"
"net/url"
"strconv"
"strings"
"time"
"maze.io/archlinux.v0/tarball"
)
// Errors
var (
ErrNoPackageName = errors.New("archlinux: name is missing")
ErrNoPackageVersion = errors.New("archlinux: version is missing")
ErrNoPackageRelease = errors.New("archlinux: release is missing")
)
// Namer is an interface for instances that have a name, such as *os.File
type Namer interface {
// Name returns the instance name
Name() string
}
// Package describes an Arch Linux package
type Package struct {
Name string
Base string
Version string
Release string
Epoch int
Description string
Arch []string
URL *url.URL `json:"-"`
License []string
Groups []string
Depends []string
MakeDepends []string
CheckDepends []string
// Filename is the name of the underlying archive or PKGBUILD
Filename string
// Name of the package
Name string
// Base of the package, if this package is a member of a split package
Base string
// Version number
Version string
// Release number
Release string
// Epoch
Epoch int
// Description of the package
Description string
// Arch slice of supported architectures
Arch []string
// URL of the project
URL *URL
// License one or more licenses
License []string
// Groups are package groups
Groups []string
// Depends are the packages this package depends on
Depends []string
// MakeDepends are the packages this package depends on for building
MakeDepends []string
// CheckDepends are the packages this package depends on for running the checks during build
CheckDepends []string
// OptionalDepends are the packages this package recommends having installed
OptionalDepends []string
Provides []string
Conflicts []string
Replaces []string
Backup []string
Options []string
MD5 []byte
SHA1 []byte
SHA256 []byte
SHA384 []byte
SHA512 []byte
PGPSignature []byte
// Provides are virtual package names provided by this package
Provides []string
// Conflicts are packages that conflict with this package
Conflicts []string
// Replaces are packages that will be replaced if this package is installed
Replaces []string
// Backup are the files that will be backed up to .pacsave if they exist on the file system if this package is installed
Backup []string
// MD5 is the MD-5 hex digest of the package file
MD5 []byte
// SHA1 is the SHA-1 hex digest of the package file
SHA1 []byte
// SHA256 is the SHA-256 hex digest of the package file
SHA256 []byte
// SHA384 is the SHA-384 hex digest of the package file
SHA384 []byte
// SHA512 is the SHA-512 hex digest of the package file
SHA512 []byte
// PGPSignature is the deteched PGP signature base64 encoded digest of the package file
PGPSignature []byte
// Size of the installed package
Size int64
// CompressedSize of the archive
CompressedSize int64
// Options are the makepkg build options used for this package
Options []string
// BuildEnv are the options during the build
BuildEnv []string
// BuildDate is the date the package was built
BuildDate Timestamp
// Packager name and email
Packager string
// Maintainers for this package
Maintainers []string
// Contributors to this package
Contributors []string
}
const unknown = "unknown"
func (pkg Package) String() string {
var s = make([]string, 3)
if pkg.Name != "" {
s[0] = pkg.Name
} else {
s[0] = "unknown"
s[0] = unknown
}
if pkg.Version != "" {
s[1] = pkg.Version
} else {
s[1] = "unknown"
s[1] = unknown
}
if pkg.Release != "" {
s[2] = pkg.Release
} else {
s[2] = "unknown"
s[2] = unknown
}
return strings.Join(s, "-")
}
// ReadPackage reads a package description from a package tarball.
func ReadPackage(r io.Reader) (*Package, error) {
t, err := tarball.NewReader(r)
if err != nil {
return nil, err
}
pkg := new(Package)
if n, ok := r.(Namer); ok {
pkg.Filename = n.Name()
}
// Scan the contents of the tarball
for {
h, err := t.Next()
if err == io.EOF {
return pkg, nil
} else if err != nil {
return nil, err
}
switch strings.TrimLeft(h.Name, "/") {
case ".BUILDINFO":
if err = pkg.readBuildInfo(t); err != nil {
return nil, err
}
case ".PKGINFO":
if err = pkg.readPackageInfo(t); err != nil {
return nil, err
}
}
}
}
// ReadPackageInfo reads a package description from a .PKGINFO file
func ReadPackageInfo(r io.Reader) (*Package, error) {
pkg := new(Package)
if n, ok := r.(Namer); ok {
pkg.Filename = n.Name()
}
if err := pkg.readPackageInfo(r); err != nil {
return nil, err
}
if pkg.Name == "" {
return nil, ErrNoPackageName
}
if pkg.Version == "" {
return nil, ErrNoPackageVersion
}
if pkg.Release == "" {
return nil, ErrNoPackageRelease
}
return pkg, nil
}
func (pkg *Package) readBuildInfo(r io.Reader) (err error) {
s := bufio.NewScanner(r)
for s.Scan() {
if err = s.Err(); err != nil {
if err == io.EOF {
err = nil
}
break
}
fields := strings.SplitN(s.Text(), " = ", 2)
if len(fields) != 2 {
continue
}
switch fields[0] {
case "buildenv":
pkg.BuildEnv = append(pkg.BuildEnv, fields[1])
case "options":
pkg.Options = append(pkg.Options, fields[1])
}
}
return
}
func (pkg *Package) readPackageInfo(r io.Reader) (err error) {
s := bufio.NewScanner(r)
for s.Scan() {
if err = s.Err(); err != nil {
if err == io.EOF {
err = nil
}
break
}
fields := strings.SplitN(s.Text(), " = ", 2)
if len(fields) != 2 {
continue
}
switch fields[0] {
case "pkgname":
pkg.Name = fields[1]
case "pkgbase":
pkg.Base = fields[1]
case "pkgver":
if i := strings.IndexByte(fields[1], '-'); i != -1 {
pkg.Version, pkg.Release = fields[1][:i], fields[1][i+1:]
} else {
return fmt.Errorf("archlinux: invalid version %q", fields[1])
}
case "pkgdesc":
pkg.Description = fields[1]
case "url":
if pkg.URL, err = ParseURL(fields[1]); err != nil {
return fmt.Errorf("archlinux: invalid url %q: %v", fields[1], err)
}
case "builddate":
var t int64
if t, err = strconv.ParseInt(fields[1], 10, 64); err != nil {
return fmt.Errorf("archlinux: invalid build date %q: %v", fields[1], err)
}
pkg.BuildDate = Timestamp(time.Unix(t, 0))
case "packager":
pkg.Packager = fields[1]
case "size":
if pkg.Size, err = strconv.ParseInt(fields[1], 10, 64); err != nil {
return fmt.Errorf("archlinux: invalid size %q: %v", fields[1], err)
}
case "arch":
pkg.Arch = append(pkg.Arch, fields[1])
case "group":
pkg.Groups = append(pkg.Groups, fields[1])
case "license":
pkg.License = append(pkg.License, fields[1])
case "backup":
pkg.Backup = append(pkg.Backup, fields[1])
case "replaces":
pkg.Replaces = append(pkg.Replaces, fields[1])
case "depend":
pkg.Depends = append(pkg.Depends, fields[1])
case "conflict":
pkg.Conflicts = append(pkg.Conflicts, fields[1])
case "provides":
pkg.Provides = append(pkg.Provides, fields[1])
case "optdepend":
pkg.OptionalDepends = append(pkg.OptionalDepends, fields[1])
case "makedepend":
pkg.MakeDepends = append(pkg.MakeDepends, fields[1])
case "checkdepend":
pkg.CheckDepends = append(pkg.CheckDepends, fields[1])
default:
return fmt.Errorf("archlinux: unknown PKGINFO field %q", fields[0])
}
}
return
}

+ 232
- 0
package_test.go
File diff suppressed because it is too large
View File


+ 11
- 2
tarball/register.go View File

@ -7,7 +7,10 @@ import (
"sync"
)
var ErrDuplicateOpener = errors.New("tarball: duplicate opener for magic")
// Common errors
var (
ErrDuplicateOpener = errors.New("tarball: duplicate opener for magic")
)
var (
mu sync.Mutex
@ -15,8 +18,12 @@ var (
magicBufSize = 1
)
// OpenerFunc is a function that converts a (plain) io.Reader into a
// decompressed io.ReadCloser.
type OpenerFunc func(io.Reader) (io.ReadCloser, error)
// Register a new decoder. The supplied magic will be matched against the
// first byte read from the io.Reader by the Open function.
func Register(magic string, fn OpenerFunc) (err error) {
mu.Lock()
defer mu.Unlock()
@ -34,9 +41,11 @@ type nullCloser struct {
io.Reader
}
// Close does nothing
func (nc nullCloser) Close() error { return nil }
// Opener opens a tarball (with compression)
// Open opens a tarball (with compression), it is expected that a plain
// reader is returned, suitable for use with e.g. tar.NewReader.
func Open(r io.Reader) (io.ReadCloser, error) {
b := make([]byte, magicBufSize)
if _, err := io.ReadFull(r, b); err != nil {


BIN
tarball/testdata/testdata.tar View File


BIN
tarball/testdata/testdata.tar.Z View File


BIN
tarball/testdata/testdata.tar.bz2 View File


BIN
tarball/testdata/testdata.tar.gz View File


BIN
tarball/testdata/testdata.tar.lzo View File


BIN
tarball/testdata/testdata.tar.xz View File


+ 1
- 0
tarball/testdata/testdata.tar.zz View File

@ -0,0 +1 @@
789cedce310ec2300c40d11c253720214d384f24982bb5e1fe441d1818602ad37bcbb7640f1e8f7ddcfbe897709e34dd6a3d3a7df69873c9ada46b5a5a0b29e73213eb893fbd3df7d1b718c3b6aee3dbddaf3d000000000000000000fcd10b212c1348

+ 76
- 0
type.go View File

@ -0,0 +1,76 @@
package archlinux
import (
"encoding/json"
"net/url"
"time"
)
// Timestamp is a UNIX timestamp
type Timestamp time.Time
// Time converts back to time.Time
func (t Timestamp) Time() time.Time {
return time.Time(t)
}
// MarshalJSON converts the timestamp to a JSON encoded UNIX timestamp
func (t *Timestamp) MarshalJSON() ([]byte, error) {
return json.Marshal((*time.Time)(t).Unix())
}
// UnmarshalJSON parses a JSON encoded UNIX timestamp to time
func (t *Timestamp) UnmarshalJSON(data []byte) error {
var i int64
if err := json.Unmarshal(data, i); err != nil {
return err
}
*t = Timestamp(time.Unix(i, 0))
return nil
}
// URL is a string encoded URL
type URL url.URL
// ParseURL is like url.Parse
func ParseURL(s string) (*URL, error) {
u, err := url.Parse(s)
if err != nil {
return nil, err
}
return (*URL)(u), nil
}
func (u *URL) String() string {
return (*url.URL)(u).String()
}
// URL converts the URL back to an url.URL
func (u *URL) URL() *url.URL {
return (*url.URL)(u)
}
// MarshalJSON marshals the URL into a JSON string
func (u *URL) MarshalJSON() ([]byte, error) {
var s string
if u != nil {
s = u.String()
}
return json.Marshal(s)
}
// UnmarshalJSON unmarshals a string into an URL
func (u *URL) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
if s != "" {
t, err := url.Parse(s)
if err != nil {
return err
}
*u = URL(*t)
}
return nil
}

Loading…
Cancel
Save