Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
311 lines (261 sloc) 7.32 KB
package srcinfo
import (
"fmt"
"io/ioutil"
"strings"
)
// parser is used to track our current state as we parse the srcinfo.
type parser struct {
// srcinfo is a Pointer to the Srcinfo we are currently building.
srcinfo *Srcinfo
// seenPkgnames is a set of pkgnames we have seen
seenPkgnames map[string]struct{}
}
func (psr *parser) currentPackage() (*Package, error) {
if psr.srcinfo.Pkgbase == "" {
return nil, fmt.Errorf("Not in pkgbase or pkgname")
} else if len(psr.srcinfo.Packages) == 0 {
return &psr.srcinfo.Package, nil
} else {
return &psr.srcinfo.Packages[len(psr.srcinfo.Packages) - 1], nil
}
}
func (psr *parser) setHeaderOrField(key, value string) error {
pkgbase := &psr.srcinfo.PackageBase
switch key {
case "pkgbase":
if psr.srcinfo.Pkgbase != "" {
return fmt.Errorf("key \"%s\" can not occur after pkgbase or pkgname", key)
}
pkgbase.Pkgbase = value
return nil
case "pkgname":
if psr.srcinfo.Pkgbase == "" {
return fmt.Errorf("key \"%s\" can not occur before pkgbase", key)
}
if _, ok := psr.seenPkgnames[value]; ok {
return fmt.Errorf("pkgname \"%s\" can not occur more than once", value)
}
psr.seenPkgnames[value] = struct{}{}
psr.srcinfo.Packages = append(psr.srcinfo.Packages, Package{Pkgname: value})
return nil
}
if psr.srcinfo.Pkgbase == "" {
return fmt.Errorf("key \"%s\" can not occur before pkgbase or pkgname", key)
}
return psr.setField(key, value)
}
func (psr *parser) setField(archKey, value string) error {
pkg, err := psr.currentPackage()
if err != nil {
return err
}
pkgbase := &psr.srcinfo.PackageBase
key, arch := splitArchFromKey(archKey)
err = checkArch(psr.srcinfo.Arch, archKey, arch)
if err != nil {
return err
}
if value == "" {
value = EmptyOverride
}
// pkgbase only + not arch dependent
found := true
switch archKey {
case "pkgver":
pkgbase.Pkgver = value
case "pkgrel":
pkgbase.Pkgrel = value
case "epoch":
pkgbase.Epoch = value
case "validpgpkeys":
pkgbase.ValidPGPKeys = append(pkgbase.ValidPGPKeys, value)
case "noextract":
pkgbase.NoExtract = append(pkgbase.NoExtract, value)
default:
found = false
}
if found {
if len(psr.srcinfo.Packages) > 0 {
return fmt.Errorf("key \"%s\" can not occur after pkgname", archKey)
}
return nil
}
// pkgbase only + arch dependent
found = true
switch key {
case "source":
pkgbase.Source = append(pkgbase.Source, ArchString{arch, value})
case "md5sums":
pkgbase.MD5Sums = append(pkgbase.MD5Sums, ArchString{arch, value})
case "sha1sums":
pkgbase.SHA1Sums = append(pkgbase.SHA1Sums, ArchString{arch, value})
case "sha224sums":
pkgbase.SHA224Sums = append(pkgbase.SHA224Sums, ArchString{arch, value})
case "sha256sums":
pkgbase.SHA256Sums = append(pkgbase.SHA256Sums, ArchString{arch, value})
case "sha384sums":
pkgbase.SHA384Sums = append(pkgbase.SHA384Sums, ArchString{arch, value})
case "sha512sums":
pkgbase.SHA512Sums = append(pkgbase.SHA512Sums, ArchString{arch, value})
case "makedepends":
pkgbase.MakeDepends = append(pkgbase.MakeDepends, ArchString{arch, value})
case "checkdepends":
pkgbase.CheckDepends = append(pkgbase.CheckDepends, ArchString{arch, value})
default:
found = false
}
if found {
if len(psr.srcinfo.Packages) > 0 {
return fmt.Errorf("key \"%s\" can not occur after pkgname", archKey)
}
return nil
}
// pkgbase or pkgname + not arch dependent
found = true
switch archKey {
case "pkgdesc":
pkg.Pkgdesc = value
case "url":
pkg.URL = value
case "license":
pkg.License = append(pkg.License, value)
case "install":
pkg.Install = value
case "changelog":
pkg.Changelog = value
case "groups":
pkg.Groups = append(pkg.Groups, value)
case "arch":
pkg.Arch = append(pkg.Arch, value)
case "backup":
pkg.Backup = append(pkg.Backup, value)
case "options":
pkg.Options = append(pkg.Options, value)
default:
found = false
}
if found {
return nil
}
// pkgbase or pkgname + arch dependent
switch key {
case "depends":
pkg.Depends = append(pkg.Depends, ArchString{arch, value})
case "optdepends":
pkg.OptDepends = append(pkg.OptDepends, ArchString{arch, value})
case "conflicts":
pkg.Conflicts = append(pkg.Conflicts, ArchString{arch, value})
case "provides":
pkg.Provides = append(pkg.Provides, ArchString{arch, value})
case "replaces":
pkg.Replaces = append(pkg.Replaces, ArchString{arch, value})
default:
return fmt.Errorf("Unknown key: \"%s\"", archKey)
}
return nil
}
func parse(data string) (*Srcinfo, error) {
psr := &parser{
&Srcinfo{},
make(map[string]struct{}),
}
lines := strings.Split(data, "\n")
for n, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, err := splitPair(line)
if err != nil {
return nil, Error(n+1, line, err.Error())
}
err = psr.setHeaderOrField(key, value)
if err != nil {
return nil, Error(n+1, line, err.Error())
}
}
if psr.srcinfo.Pkgbase == "" {
return nil, fmt.Errorf("No pkgbase field")
}
if len(psr.srcinfo.Packages) == 0 {
return nil, fmt.Errorf("No pkgname field")
}
if psr.srcinfo.Pkgver == "" {
return nil, fmt.Errorf("No pkgver field")
}
if psr.srcinfo.Pkgrel == "" {
return nil, fmt.Errorf("No pkgrel field")
}
if len(psr.srcinfo.Arch) == 0 {
return nil, fmt.Errorf("No arch field")
}
return psr.srcinfo, nil
}
// splitPair splits a key value string in the form of "key = value",
// whitespace being ignored. The key and the value is returned.
func splitPair(line string) (string, string, error) {
split := strings.SplitN(line, "=", 2)
if len(split) != 2 {
return "", "", fmt.Errorf("Line does not contain =")
}
key := strings.TrimSpace(split[0])
value := strings.TrimSpace(split[1])
if key == "" {
return "", "", fmt.Errorf("Key is empty")
}
return key, value, nil
}
// splitArchFromKey splits up architecture dependent field names, separating
// the field name from the architecture they depend on.
func splitArchFromKey(key string) (string, string) {
split := strings.SplitN(key, "_", 2)
if len(split) == 2 {
return split[0], split[1]
}
return split[0], ""
}
// checkArg checks that the arch from an arch dependent string is actually
// defined inside of the srcinfo and speicifly disallows the arch "any" as it
// is not a real arch
func checkArch(arches []string, key string, arch string) error {
if arch == "" {
return nil
}
if arch == "any" {
return fmt.Errorf("Invalid key \"%s\" arch \"%s\" is not allowed", key, arch)
}
for _, a := range arches {
if a == arch {
return nil
}
}
return fmt.Errorf("Invalid key \"%s\" unsupported arch \"%s\"", key, arch)
}
// ParseFile parses a srcinfo file as specified by path.
func ParseFile(path string) (*Srcinfo, error) {
file, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("Unable to read file: %s: %s", path, err.Error())
}
return Parse(string(file))
}
// Parse parses a srcinfo in string form. Parsing will fail if:
// A srcinfo does not contain all required fields
// The same pkgname is specified more then once
// arch is missing
// pkgver is mising
// pkgrel is missing
// An architecture specific field is defined for an architecture that does not exist
// An unknown key is specified
// An empty value is specified
//
// Required fields are:
// pkgbase
// pkname
// arch
// pkgrel
// pkgver
func Parse(data string) (*Srcinfo, error) {
return parse(data)
}