pm/available.go
stephen mcquay 14a456f006
Adds EXTRAORDINARILY Installable implementation
We want the simplest satisfiability solver we can have, but one that
chases transitive dependencies. This one only checks the requested
packages exist in the availability database.
2018-03-03 22:33:52 -08:00

188 lines
4.0 KiB
Go

package pm
import (
"fmt"
"net/url"
"sort"
"strings"
"github.com/pkg/errors"
)
// Name exists to document the keys in Available
type Name string
// Names is a slice of names ... with sorting!
type Names []Name
func (n Names) Len() int { return len(n) }
func (n Names) Swap(a, b int) { n[a], n[b] = n[b], n[a] }
func (n Names) Less(a, b int) bool { return n[a] < n[b] }
// Version exists to document the keys in Available
type Version string
// Versions is a slice of Version ... with sorting!
type Versions []Version
// TODO (sm): make this semver sort?
func (v Versions) Len() int { return len(v) }
func (v Versions) Swap(a, b int) { v[a], v[b] = v[b], v[a] }
func (v Versions) Less(a, b int) bool { return v[a] < v[b] }
type label struct {
n Name
v Version
}
type labels []label
// TODO (sm): make this semver sort?
func (n labels) Len() int { return len(n) }
func (n labels) Swap(a, b int) { n[a], n[b] = n[b], n[a] }
func (n labels) Less(a, b int) bool {
if n[a].n != n[b].n {
return n[a].n < n[b].n
}
return n[a].v < n[b].v
}
// Available is the structure used to represent the collection of all packages
// that can be installed.
type Available map[Name]map[Version]Meta
// Get returns the meta stored at a[n][v] or an error explaining why it could
// not be Get.
func (a Available) Get(n Name, v Version) (Meta, error) {
if _, ok := a[n]; !ok {
return Meta{}, errors.Errorf("could not find package named %q", n)
}
if v == "" {
if len(a[n]) == 0 {
return Meta{}, errors.Errorf("no configured versions for %q", n)
}
vers := Versions{}
for ver := range a[n] {
vers = append(vers, ver)
}
sort.Sort(vers)
v = vers[len(vers)-1]
}
if _, ok := a[n][v]; !ok {
return Meta{}, errors.Errorf("could not find %v@%v in database", n, v)
}
return a[n][v], nil
}
// Add inserts m into a.
func (a Available) Add(m Meta) error {
if _, err := m.Valid(); err != nil {
return errors.Wrap(err, "invalid meta")
}
if _, ok := a[Name(m.Name)]; !ok {
a[m.Name] = map[Version]Meta{}
}
a[m.Name][m.Version] = m
return nil
}
// Update inserts all data from o into a.
func (a Available) Update(o Available) error {
for _, vers := range o {
for _, m := range vers {
if err := a.Add(m); err != nil {
return errors.Wrap(err, "adding")
}
}
}
return nil
}
// SetRemote adds the information in the url to the database.
func (a Available) SetRemote(u url.URL) {
for n, vers := range a {
for v := range vers {
m := a[n][v]
m.Remote = u
a[n][v] = m
}
}
}
// Traverse returns a chan of Meta that will be sanely sorted.
func (a Available) Traverse() <-chan Meta {
r := make(chan Meta)
go func() {
names := Names{}
nvs := map[Name]Versions{}
for n, vers := range a {
names = append(names, n)
for v := range vers {
nvs[n] = append(nvs[n], v)
}
sort.Sort(nvs[n])
}
sort.Sort(names)
for _, n := range names {
for _, v := range nvs[n] {
r <- a[n][v]
}
}
close(r)
}()
return r
}
func labelForString(s string) (label, error) {
r := label{}
c := strings.Count(s, "@")
switch c {
case 0:
r.n = Name(s)
case 1:
sp := strings.Split(s, "@")
r.n, r.v = Name(sp[0]), Version(sp[1])
default:
return r, fmt.Errorf("unexpected number of '@' found, got %v, want 1", c)
}
if r.n == "" {
return r, fmt.Errorf("name cannot be empty")
}
return r, nil
}
// Installable calculates if the packages requested in "in" can be installed.
func (a Available) Installable(in []string) (Metas, error) {
ls := labels{}
for _, i := range in {
l, err := labelForString(i)
if err != nil {
return nil, errors.Wrap(err, "parsing name/version")
}
ls = append(ls, l)
}
seen := map[Name]bool{}
for _, l := range ls {
if _, ok := seen[l.n]; ok {
return nil, fmt.Errorf("can only ask to install %q once", l.n)
}
seen[l.n] = true
}
ms := Metas{}
for _, l := range ls {
m, err := a.Get(l.n, l.v)
if err != nil {
return ms, errors.Wrapf(err, "getting %v", l)
}
ms = append(ms, m)
}
return ms, nil
}