@@ -1,11 +1,15 @@ | |||
package archlinux | |||
import ( | |||
"encoding/base64" | |||
"encoding/hex" | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"io" | |||
"net/url" | |||
"reflect" | |||
"strconv" | |||
"strings" | |||
"time" | |||
@@ -29,22 +33,22 @@ type Package struct { | |||
Filename string | |||
// Name of the package | |||
Name string `pkginfo:"pkgname"` | |||
Name string `pkginfo:"pkgname" srcinfo:"pkgname"` | |||
// Base of the package, if this package is a member of a split package | |||
Base string `pkginfo:"pkgbase"` | |||
Base string `pkginfo:"pkgbase" srcinfo:"pkgbase"` | |||
// Version number | |||
Version string `pkgdesc:"VERSION,evr" pkginfo:"pkgver,evr"` | |||
Version string `pkgdesc:"VERSION,evr" pkginfo:"pkgver,evr" srcinfo:"pkgver"` | |||
// Release number | |||
Release string `pkgdesc:"-" pkginfo:"-"` | |||
Release string `pkgdesc:"-" pkginfo:"-" srcinfo:"pkgrel"` | |||
// Epoch | |||
Epoch int `pkgdesc:"-" pkginfo:"-"` | |||
Epoch int64 `pkgdesc:"-" pkginfo:"-" srcinfo:"epoch"` | |||
// Description of the package | |||
Description string `pkgdesc:"DESC" pkginfo:"pkgdesc"` | |||
Description string `pkgdesc:"DESC" pkginfo:"pkgdesc" srcinfo:"pkgdesc"` | |||
// Arch slice of supported architectures | |||
Arch []string | |||
@@ -59,19 +63,19 @@ type Package struct { | |||
Groups []string | |||
// Depends are the packages this package depends on | |||
Depends []string `pkgdesc:"DEPENDS" pkginfo:"depend"` | |||
Depends []string `pkgdesc:"DEPENDS" pkginfo:"depend" srcinfo:"depends"` | |||
// MakeDepends are the packages this package depends on for building | |||
MakeDepends []string `pkgdesc:"MAKEDEPENDS" pkginfo:"makedepend"` | |||
MakeDepends []string `pkgdesc:"MAKEDEPENDS" pkginfo:"makedepend" srcinfo:"makedepends"` | |||
// CheckDepends are the packages this package depends on for running the | |||
// checks during build | |||
CheckDepends []string `pkgdesc:"CHECKDEPENDS" pkginfo:"checkdepend"` | |||
CheckDepends []string `pkgdesc:"CHECKDEPENDS" pkginfo:"checkdepend" srcinfo:"checkdepends"` | |||
// OptionalDepends are the packages this package recommends having | |||
// installed. Note that these values may contain comments, indicated by | |||
// "spec: comment" (the separator is ": ") | |||
OptionalDepends []string `pkgdesc:"OPTDEPENDS" pkginfo:"optdepend"` | |||
OptionalDepends []string `pkgdesc:"OPTDEPENDS" pkginfo:"optdepend" srcinfo:"optdepends"` | |||
// Provides are virtual package names provided by this package | |||
Provides []string | |||
@@ -232,22 +236,110 @@ func ReadPackage(r io.Reader) (*Package, error) { | |||
} | |||
} | |||
func (pkg *Package) parseFields(parseField func(reflect.StructField) (string, string)) (values map[string]reflect.Value, encodings map[string]string) { | |||
func parsePackageFields(parseField func(reflect.StructField) (string, string), v ...interface{}) (values map[string]reflect.Value, encodings map[string]string) { | |||
values = make(map[string]reflect.Value) | |||
encodings = make(map[string]string) | |||
var ( | |||
value = reflect.Indirect(reflect.ValueOf(pkg)) | |||
typ = value.Type() | |||
) | |||
for i := 0; i < value.NumField(); i++ { | |||
fieldValue := value.Field(i) | |||
name, encoding := parseField(typ.Field(i)) | |||
if name == "" { | |||
// No name means ignored field | |||
continue | |||
for _, o := range v { | |||
var ( | |||
value = reflect.Indirect(reflect.ValueOf(o)) | |||
typ = value.Type() | |||
) | |||
for i := 0; i < value.NumField(); i++ { | |||
var ( | |||
fieldValue = value.Field(i) | |||
name, encoding = parseField(typ.Field(i)) | |||
) | |||
if name == "" { | |||
// No name means ignored field | |||
continue | |||
} | |||
values[name] = fieldValue | |||
encodings[name] = encoding | |||
} | |||
} | |||
return | |||
} | |||
// setPackageValue updates a Package-like struct field with the corresponding | |||
// value; the struct is passed as interface{} o; for evr decoding it will | |||
// iterate over the struct fields and attempts to locate "Release" string and | |||
// "Epoch" int and update them accordingly | |||
func setPackageValue(o interface{}, v reflect.Value, value, encoding string) (err error) { | |||
switch v.Interface().(type) { | |||
case string: | |||
switch encoding { | |||
case "": | |||
v.SetString(value) | |||
case "evr": | |||
p := version.Parse(value) | |||
if p.Version == "" { | |||
return ErrNoPackageVersion | |||
} | |||
v.SetString(p.Version) | |||
ov := reflect.Indirect(reflect.ValueOf(o)) | |||
ot := ov.Type() | |||
for i := 0; i < ov.NumField(); i++ { | |||
switch ot.Field(i).Name { | |||
case "Release": | |||
ov.Field(i).SetString(p.Release) | |||
case "Epoch": | |||
if p.Epoch == "" || p.Epoch == "0" { | |||
ov.Field(i).SetInt(0) | |||
} else { | |||
var epoch int64 | |||
if epoch, err = strconv.ParseInt(p.Epoch, 10, 64); err != nil { | |||
return fmt.Errorf("archlinux: invalid epoch %q: %v", p.Epoch, err) | |||
} | |||
ov.Field(i).SetInt(epoch) | |||
} | |||
} | |||
} | |||
} | |||
case int64: | |||
var i int64 | |||
if i, err = strconv.ParseInt(value, 10, 64); err != nil { | |||
return fmt.Errorf("archlinux: invalid %s %q: %v", v.Type().Name(), value, err) | |||
} | |||
v.SetInt(i) | |||
case []string: | |||
v.Set(reflect.Append(v, reflect.ValueOf(value))) | |||
case []byte: | |||
switch encoding { | |||
case "": | |||
v.SetBytes([]byte(value)) | |||
case "base64": | |||
var b []byte | |||
if b, err = base64.StdEncoding.DecodeString(value); err != nil { | |||
return fmt.Errorf("archlinux: invalid base64 value %q: %v", value, err) | |||
} | |||
v.SetBytes(b) | |||
case "hex": | |||
var b []byte | |||
if b, err = hex.DecodeString(value); err != nil { | |||
return fmt.Errorf("archlinux: invalid hex encoded value %q: %v", value, err) | |||
} | |||
v.SetBytes(b) | |||
} | |||
case [][]byte: | |||
switch encoding { | |||
case "": | |||
v.Set(reflect.Append(v, reflect.ValueOf([]byte(value)))) | |||
} | |||
case time.Time: | |||
var i int64 | |||
if i, err = strconv.ParseInt(value, 10, 64); err != nil { | |||
return fmt.Errorf("archlinux: invalid time %q: %v", value, err) | |||
} | |||
v.Set(reflect.ValueOf(time.Unix(i, 0).UTC())) | |||
case *url.URL: | |||
var u *url.URL | |||
if u, err = url.Parse(value); err != nil { | |||
return fmt.Errorf("archlinux: invalid URL %q: %v", value, err) | |||
} | |||
values[name] = fieldValue | |||
encodings[name] = encoding | |||
v.Set(reflect.ValueOf(u)) | |||
default: | |||
return fmt.Errorf("archlinux: unsupported field type %T", v.Interface()) | |||
} | |||
return | |||
} | |||
@@ -8,11 +8,8 @@ import ( | |||
"io" | |||
"net/url" | |||
"reflect" | |||
"strconv" | |||
"strings" | |||
"time" | |||
"maze.io/archlinux.v0/version" | |||
) | |||
// ReadPackageDesc reads a package description from a desc file | |||
@@ -37,30 +34,13 @@ func ReadPackageDesc(r io.Reader) (*Package, error) { | |||
} | |||
func (pkg *Package) readPackageDesc(r io.Reader) (err error) { | |||
var ( | |||
fields = make(map[string]reflect.Value) | |||
encodings = make(map[string]string) | |||
value = reflect.Indirect(reflect.ValueOf(pkg)) | |||
typ = value.Type() | |||
) | |||
for i := 0; i < value.NumField(); i++ { | |||
fieldValue := value.Field(i) | |||
fieldType := typ.Field(i) | |||
name, encoding := parseFieldDesc(fieldType) | |||
if name == "" { | |||
// No name means ignored field | |||
continue | |||
} | |||
fields[name] = fieldValue | |||
encodings[name] = encoding | |||
} | |||
var ( | |||
s = bufio.NewScanner(r) | |||
line string | |||
lines []string | |||
section string | |||
) | |||
values, encodings := parsePackageFields(parseFieldDesc, pkg) | |||
for s.Scan() { | |||
if err = s.Err(); err != nil { | |||
if err == io.EOF { | |||
@@ -71,59 +51,73 @@ func (pkg *Package) readPackageDesc(r io.Reader) (err error) { | |||
line = s.Text() | |||
if len(line) == 0 { | |||
v, ok := fields[section] | |||
v, ok := values[section] | |||
if !ok { | |||
return fmt.Errorf("archlinux: unsupported section %q", section) | |||
} | |||
switch v.Interface().(type) { | |||
case string: | |||
switch encodings[section] { | |||
case "": | |||
v.SetString(lines[0]) | |||
case evrEncoding: | |||
p := version.Parse(lines[0]) | |||
pkg.Version = p.Version | |||
pkg.Release = p.Release | |||
if p.Epoch != "" { | |||
if pkg.Epoch, err = strconv.Atoi(p.Epoch); err != nil { | |||
return fmt.Errorf("archlinux: invalid epoch %q: %v", p.Epoch, err) | |||
} | |||
case []string: | |||
for _, line = range lines { | |||
if err = setPackageValue(pkg, v, line, encodings[section]); err != nil { | |||
return | |||
} | |||
} | |||
case []string: | |||
c := make([]string, len(lines)) | |||
copy(c, lines) | |||
v.Set(reflect.ValueOf(c)) | |||
case []byte: | |||
switch encodings[section] { | |||
case "": | |||
v.SetBytes([]byte(strings.Join(lines, "\n"))) | |||
case base64Encoding: | |||
var b []byte | |||
if b, err = base64.StdEncoding.DecodeString(lines[0]); err != nil { | |||
default: | |||
if err = setPackageValue(pkg, v, strings.Join(lines, "\n"), encodings[section]); err != nil { | |||
return | |||
} | |||
} | |||
/* | |||
switch v.Interface().(type) { | |||
case string: | |||
switch encodings[section] { | |||
case "": | |||
v.SetString(lines[0]) | |||
case evrEncoding: | |||
p := version.Parse(lines[0]) | |||
pkg.Version = p.Version | |||
pkg.Release = p.Release | |||
if p.Epoch != "" { | |||
if pkg.Epoch, err = strconv.Atoi(p.Epoch); err != nil { | |||
return fmt.Errorf("archlinux: invalid epoch %q: %v", p.Epoch, err) | |||
} | |||
} | |||
} | |||
case []string: | |||
c := make([]string, len(lines)) | |||
copy(c, lines) | |||
v.Set(reflect.ValueOf(c)) | |||
case []byte: | |||
switch encodings[section] { | |||
case "": | |||
v.SetBytes([]byte(strings.Join(lines, "\n"))) | |||
case base64Encoding: | |||
var b []byte | |||
if b, err = base64.StdEncoding.DecodeString(lines[0]); err != nil { | |||
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err) | |||
} | |||
v.SetBytes(b) | |||
case hexEncoding: | |||
var b []byte | |||
if b, err = hex.DecodeString(lines[0]); err != nil { | |||
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err) | |||
} | |||
v.SetBytes(b) | |||
} | |||
case time.Time: | |||
var i int64 | |||
if i, err = strconv.ParseInt(lines[0], 10, 64); err != nil { | |||
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err) | |||
} | |||
v.SetBytes(b) | |||
case hexEncoding: | |||
var b []byte | |||
if b, err = hex.DecodeString(lines[0]); err != nil { | |||
v.Set(reflect.ValueOf(time.Unix(i, 0))) | |||
case *url.URL: | |||
var u *url.URL | |||
if u, err = url.Parse(lines[0]); err != nil { | |||
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err) | |||
} | |||
v.SetBytes(b) | |||
v.Set(reflect.ValueOf(u)) | |||
} | |||
case time.Time: | |||
var i int64 | |||
if i, err = strconv.ParseInt(lines[0], 10, 64); err != nil { | |||
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err) | |||
} | |||
v.Set(reflect.ValueOf(time.Unix(i, 0))) | |||
case *url.URL: | |||
var u *url.URL | |||
if u, err = url.Parse(lines[0]); err != nil { | |||
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err) | |||
} | |||
v.Set(reflect.ValueOf(u)) | |||
} | |||
*/ | |||
} else if line[0] == '%' && line[len(line)-1] == '%' { | |||
section = line[1 : len(line)-1] | |||
lines = lines[:0] | |||
@@ -8,11 +8,8 @@ import ( | |||
"io" | |||
"net/url" | |||
"reflect" | |||
"strconv" | |||
"strings" | |||
"time" | |||
"maze.io/archlinux.v0/version" | |||
) | |||
// ReadPackageInfo reads a package description from a .PKGINFO file | |||
@@ -62,7 +59,7 @@ func (pkg *Package) readBuildInfo(r io.Reader) (err error) { | |||
} | |||
func (pkg *Package) readPackageInfo(r io.Reader) (err error) { | |||
values, encodings := pkg.parseFields(parseFieldInfo) | |||
values, encodings := parsePackageFields(parseFieldInfo, pkg) | |||
s := bufio.NewScanner(r) | |||
for s.Scan() { | |||
if err = s.Err(); err != nil { | |||
@@ -81,66 +78,8 @@ func (pkg *Package) readPackageInfo(r io.Reader) (err error) { | |||
if !ok { | |||
return fmt.Errorf("archlinux: unknown PKGINFO field %q", fields[0]) | |||
} | |||
encoding := encodings[fields[0]] | |||
switch v.Interface().(type) { | |||
case string: | |||
switch encoding { | |||
case "": | |||
v.SetString(fields[1]) | |||
case "evr": | |||
p := version.Parse(fields[1]) | |||
if p.Version == "" { | |||
return ErrNoPackageVersion | |||
} | |||
pkg.Version = p.Version | |||
if p.Release == "" { | |||
return ErrNoPackageRelease | |||
} | |||
pkg.Release = p.Release | |||
if p.Epoch != "" { | |||
if pkg.Epoch, err = strconv.Atoi(p.Epoch); err != nil { | |||
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err) | |||
} | |||
} | |||
} | |||
case int64: | |||
var i int64 | |||
if i, err = strconv.ParseInt(fields[1], 10, 64); err != nil { | |||
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err) | |||
} | |||
v.SetInt(i) | |||
case []string: | |||
v.Set(reflect.Append(v, reflect.ValueOf(fields[1]))) | |||
case []byte: | |||
switch encoding { | |||
case "": | |||
v.SetBytes([]byte(fields[1])) | |||
case "base64": | |||
var b []byte | |||
if b, err = base64.StdEncoding.DecodeString(fields[1]); err != nil { | |||
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err) | |||
} | |||
v.SetBytes(b) | |||
case "hex": | |||
var b []byte | |||
if b, err = hex.DecodeString(fields[1]); err != nil { | |||
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err) | |||
} | |||
v.SetBytes(b) | |||
} | |||
case time.Time: | |||
var i int64 | |||
if i, err = strconv.ParseInt(fields[1], 10, 64); err != nil { | |||
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err) | |||
} | |||
v.Set(reflect.ValueOf(time.Unix(i, 0).UTC())) | |||
case *url.URL: | |||
var u *url.URL | |||
if u, err = url.Parse(fields[1]); err != nil { | |||
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err) | |||
} | |||
v.Set(reflect.ValueOf(u)) | |||
if err = setPackageValue(pkg, v, fields[1], encodings[fields[0]]); err != nil { | |||
return | |||
} | |||
} | |||
return | |||
@@ -0,0 +1,118 @@ | |||
package archlinux | |||
import ( | |||
"bufio" | |||
"fmt" | |||
"io" | |||
"reflect" | |||
"strings" | |||
) | |||
// PackageBuild describes a PKGBUILD file | |||
type PackageBuild struct { | |||
// Package is the base package | |||
Package | |||
// Packages are the packages contained in this build | |||
Packages map[string]*Package | |||
// Source files | |||
Source []string | |||
// SourceArch are arch specific sources | |||
SourceArch map[string][]string | |||
MD5Sums [][]byte | |||
SHA1Sums [][]byte | |||
SHA256Sums [][]byte | |||
SHA384Sums [][]byte | |||
SHA512Sums [][]byte | |||
} | |||
// ReadPackageBuild reads a PKGBUILD file | |||
func ReadPackageBuild(r io.Reader) (*PackageBuild, error) { | |||
return nil, nil | |||
} | |||
func parseFieldSrcInfo(field reflect.StructField) (name, encoding string) { | |||
tag := field.Tag.Get("srcinfo") | |||
switch tag { | |||
case "-": | |||
// Ignored tag | |||
case "": | |||
// No desc tag, default to upper case field name | |||
name = strings.ToLower(field.Name) | |||
default: | |||
if i := strings.IndexByte(tag, ','); i != -1 { | |||
name, encoding = tag[:i], tag[i+1:] | |||
} else { | |||
name = tag | |||
} | |||
} | |||
return | |||
} | |||
// ReadSrcInfo reads a .SRCINFO file | |||
func ReadSrcInfo(r io.Reader) (*PackageBuild, error) { | |||
var ( | |||
build = &PackageBuild{ | |||
Packages: make(map[string]*Package), | |||
} | |||
pkg = &build.Package | |||
s = bufio.NewScanner(r) | |||
) | |||
values, encodings := parsePackageFields(parseFieldSrcInfo, build, &build.Package) | |||
for s.Scan() { | |||
if err := s.Err(); err != nil { | |||
if err == io.EOF { | |||
break | |||
} | |||
return nil, err | |||
} | |||
line := s.Text() | |||
if line == "" { | |||
continue | |||
} | |||
if line[0] == '#' { | |||
continue | |||
} | |||
if line[0] == '\t' || line[0] == ' ' { | |||
fields := strings.SplitN(strings.TrimSpace(line), " = ", 2) | |||
if len(fields) != 2 { | |||
continue | |||
} | |||
v, ok := values[fields[0]] | |||
if !ok { | |||
return nil, fmt.Errorf("archlinux: unknown .SRCINFO field %q", fields[0]) | |||
} | |||
if err := setPackageValue(pkg, v, fields[1], encodings[fields[0]]); err != nil { | |||
return nil, err | |||
} | |||
} else if strings.HasPrefix(line, "pkgbase = ") { | |||
build.Base = line[len("pkgbase = "):] | |||
build.Name = build.Base | |||
pkg = &build.Package | |||
} else if strings.HasPrefix(line, "pkgname = ") { | |||
var ( | |||
ok bool | |||
name = line[len("pkgname = "):] | |||
) | |||
if pkg, ok = build.Packages[name]; !ok { | |||
pkg = &Package{ | |||
Name: name, | |||
Base: build.Base, | |||
Version: build.Version, | |||
Release: build.Release, | |||
Epoch: build.Epoch, | |||
Arch: make([]string, len(build.Arch)), | |||
License: make([]string, len(build.License)), | |||
} | |||
copy(pkg.Arch, build.Arch) | |||
copy(pkg.License, build.License) | |||
build.Packages[name] = pkg | |||
} | |||
values, encodings = parsePackageFields(parseFieldSrcInfo, pkg) | |||
} | |||
} | |||
return build, nil | |||
} |
@@ -0,0 +1,49 @@ | |||
package archlinux | |||
import ( | |||
"bytes" | |||
"testing" | |||
) | |||
func TestReadSrcInfo(t *testing.T) { | |||
build, err := ReadSrcInfo(bytes.NewBufferString(testReadSrcInfo)) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
t.Run(build.String(), func(t *testing.T) { | |||
if l := len(build.Packages); l != 2 { | |||
t.Fatalf(`expected 2 packages, got %d`, l) | |||
} | |||
t.Run("packages", func(t *testing.T) { | |||
for name, pkg := range build.Packages { | |||
t.Run(name, func(t *testing.T) { | |||
t.Log(pkg) | |||
if l := len(pkg.Depends); l != 2 { | |||
t.Fatalf(`expected 2 dependencies, got %d`, l) | |||
} | |||
}) | |||
} | |||
}) | |||
}) | |||
} | |||
var ( | |||
testReadSrcInfo = `pkgbase = python-pyjnius | |||
pkgdesc = Python module to access Java class as Python class, using JNI. | |||
pkgver = 1.1.1 | |||
pkgrel = 1 | |||
url = https://github.com/kivy/pyjnius | |||
arch = any | |||
license = LGPL3 | |||
source = pyjnius-1.1.1.tar.gz::https://github.com/kivy/pyjnius/tarball/1.1.1 | |||
md5sums = 2d457e4761b27e6760cf54efb6201f17 | |||
pkgname = python2-pyjnius | |||
depends = java-environment | |||
depends = python2 | |||
pkgname = python-pyjnius | |||
depends = java-environment | |||
depends = python | |||
` | |||
) |