Arch Linux helpers and parsers
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

249 lines
5.7 KiB

package archlinux
import (
"bufio"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"net/url"
"reflect"
"strconv"
"strings"
"time"
"maze.io/archlinux.v0/version"
)
// ReadPackageDesc reads a package description from a desc file
func ReadPackageDesc(r io.Reader) (*Package, error) {
pkg := new(Package)
if n, ok := r.(namer); ok {
pkg.Filename = n.Name()
}
if err := pkg.readPackageDesc(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) 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
)
for s.Scan() {
if err = s.Err(); err != nil {
if err == io.EOF {
err = nil
}
break
}
line = s.Text()
if len(line) == 0 {
v, ok := fields[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:
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.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]
} else {
lines = append(lines, line)
}
}
return
}
func parseFieldDesc(field reflect.StructField) (name, encoding string) {
tag := field.Tag.Get("pkgdesc")
switch tag {
case "-":
// Ignored tag
case "":
// No desc tag, default to upper case field name
name = strings.ToUpper(field.Name)
default:
if i := strings.IndexByte(tag, ','); i != -1 {
name, encoding = strings.ToUpper(tag[:i]), tag[i+1:]
} else {
name = strings.ToUpper(tag)
}
}
return
}
var timeZero time.Time
// WritePackageDesc writes a package desc file to the target Writer.
func (pkg *Package) WritePackageDesc(w io.Writer) (int, error) {
var (
value = reflect.Indirect(reflect.ValueOf(pkg))
typ = value.Type()
total, n int
err error
)
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
}
switch v := fieldValue.Interface().(type) {
case string:
if v == "" {
continue
}
switch encoding {
case "":
// No additional encoding
case evrEncoding:
// Epoch:Version-Release
if pkg.Epoch > 0 {
v = fmt.Sprintf("%d:%s-%s", pkg.Epoch, pkg.Version, pkg.Release)
} else {
v = fmt.Sprintf("%s-%s", pkg.Version, pkg.Release)
}
}
if n, err = fmt.Fprintf(w, "%%%s%%\n%s\n\n", name, v); err != nil {
return total + n, err
}
total += n
case int64:
if v == 0 {
continue
}
if n, err = fmt.Fprintf(w, "%%%s%%\n%d\n\n", name, v); err != nil {
return total + n, err
}
total += n
case []string:
if len(v) == 0 {
continue
}
if n, err = fmt.Fprintf(w, "%%%s%%\n%s\n\n", name, strings.Join(v, "\n")); err != nil {
return total + n, err
}
total += n
case []byte:
if len(v) == 0 {
continue
}
var o []byte
switch encoding {
case "":
o = v
case base64Encoding:
o = make([]byte, base64.StdEncoding.EncodedLen(len(v)))
base64.StdEncoding.Encode(o, v)
case hexEncoding:
o = make([]byte, hex.EncodedLen(len(v)))
hex.Encode(o, v)
}
if n, err = fmt.Fprintf(w, "%%%s%%\n%s\n\n", name, o); err != nil {
return total + n, err
}
total += n
case time.Time:
if v.Equal(timeZero) {
continue
}
if n, err = fmt.Fprintf(w, "%%%s%%\n%d\n\n", name, v.Unix()); err != nil {
return total + n, err
}
total += n
case *url.URL:
if v == nil {
continue
}
if n, err = fmt.Fprintf(w, "%%%s%%\n%s\n\n", name, v); err != nil {
return total + n, err
}
total += n
default:
return total, fmt.Errorf("archlinux: can't write %T to desc", v)
}
}
return total, nil
}