Add new dependency code to replace the old code

This is a rewrite of the dependency system, It aims to be cleaner
written, with a better idea of what is needed from the start, meaning
less new code being hacked on for things that were not thought about.
This version also aims to use as many small functions as possible, for
cleaner code and better testing.

Added dep.go:
    general dependency functions
Added depPool.go:
    Replacement of depTree, dependencies were never ordered so
    a tree did not really make sense. Instead the term pool
    makes more sense.
Added depOrder.go:
    Replacement of depCatagories, This simply orders the
    dependencies, dependencies are still catagorized as repo and AUR
    but I believe this to be a better name
Added depCheck.go:
    Replaces conflicts.go and also contains the missing dependency
    code

This version is mostly the same as the old version with a few
improvments:
    Missing packages will print the full dependency tree
    Versioned dependency checking errors should be fixed
    Make depends should be calculated properly
    Experimental AUR provide searcher

This code has been added along side the old code for testing and is not
currently used by the install process. Once the install process is moved
to use this code, the old code will be removed.
This commit is contained in:
morganamilo 2018-05-08 05:06:25 +01:00
parent d442af9dcc
commit b2d3eb5c49
No known key found for this signature in database
GPG key ID: 6FE9E7996B0B082E
7 changed files with 1074 additions and 0 deletions

104
dep.go Normal file
View file

@ -0,0 +1,104 @@
package main
import (
"fmt"
"strings"
alpm "github.com/jguer/go-alpm"
rpc "github.com/mikkeloscar/aur"
)
func splitDep(dep string) (string, string, string) {
mod := ""
split := strings.FieldsFunc(dep, func(c rune) bool {
match := c == '>' || c == '<' || c == '='
if match {
mod += string(c)
}
return match
})
if len(split) == 1 {
return split[0], "", ""
}
return split[0], mod, split[1]
}
func pkgSatisfies(name, version, dep string) bool {
depName, depMod, depVersion := splitDep(dep)
if depName != name {
return false
}
return verSatisfies(version, depMod, depVersion)
}
func provideSatisfies(provide, dep string) bool {
depName, depMod, depVersion := splitDep(dep)
provideName, provideMod, provideVersion := splitDep(provide)
if provideName != depName {
return false
}
// Unversioned provieds can not satisfy a versioned dep
if provideMod == "" && depMod != "" {
return false
}
return verSatisfies(provideVersion, depMod, depVersion)
}
func verSatisfies(ver1, mod, ver2 string) bool {
switch mod {
case "=":
return alpm.VerCmp(ver1, ver2) == 0
case "<":
return alpm.VerCmp(ver1, ver2) < 0
case "<=":
return alpm.VerCmp(ver1, ver2) <= 0
case ">":
return alpm.VerCmp(ver1, ver2) > 0
case ">=":
return alpm.VerCmp(ver1, ver2) >= 0
}
return true
}
func satisfiesAur(dep string, pkg *rpc.Pkg) bool {
if pkgSatisfies(pkg.Name, pkg.Version, dep) {
return true
}
for _, provide := range pkg.Provides {
if provideSatisfies(provide, dep) {
return true
}
}
return false
}
func satisfiesRepo(dep string, pkg *alpm.Package) bool {
if pkgSatisfies(pkg.Name(), pkg.Version(), dep) {
return true
}
if pkg.Provides().ForEach(func(provide alpm.Depend) error {
if provideSatisfies(provide.String(), dep) {
return fmt.Errorf("")
}
return nil
}) != nil {
return true
}
return false
}

264
depCheck.go Normal file
View file

@ -0,0 +1,264 @@
package main
import (
"fmt"
"strings"
"sync"
alpm "github.com/jguer/go-alpm"
// gopkg "github.com/mikkeloscar/gopkgbuild"
)
func (dp *depPool) checkInnerConflict(name string, conflict string, conflicts mapStringSet) {
for _, pkg := range dp.Aur {
if pkg.Name == name {
continue
}
if satisfiesAur(conflict, pkg) {
conflicts.Add(name, pkg.Name)
}
}
for _, pkg := range dp.Repo {
if pkg.Name() == name {
continue
}
if satisfiesRepo(conflict, pkg) {
conflicts.Add(name, pkg.Name())
}
}
}
func (dp *depPool) checkForwardConflict(name string, conflict string, conflicts mapStringSet) {
dp.LocalDb.PkgCache().ForEach(func(pkg alpm.Package) error {
if pkg.Name() == name {
return nil
}
if satisfiesRepo(conflict, &pkg) {
n := pkg.Name()
if n != conflict {
n += " (" + conflict + ")"
}
conflicts.Add(name, n)
}
return nil
})
}
func (dp *depPool) checkReverseConflict(name string, conflict string, conflicts mapStringSet) {
for _, pkg := range dp.Aur {
if pkg.Name == name {
continue
}
if satisfiesAur(conflict, pkg) {
if name != conflict {
name += " (" + conflict + ")"
}
conflicts.Add(pkg.Name, name)
}
}
for _, pkg := range dp.Repo {
if pkg.Name() == name {
continue
}
if satisfiesRepo(conflict, pkg) {
if name != conflict {
name += " (" + conflict + ")"
}
conflicts.Add(pkg.Name(), name)
}
}
}
func (dp *depPool) checkInnerConflicts(conflicts mapStringSet) {
for _, pkg := range dp.Aur {
for _, conflict := range pkg.Conflicts {
dp.checkInnerConflict(pkg.Name, conflict, conflicts)
}
}
for _, pkg := range dp.Repo {
pkg.Conflicts().ForEach(func(conflict alpm.Depend) error {
dp.checkInnerConflict(pkg.Name(), conflict.String(), conflicts)
return nil
})
}
}
func (dp *depPool) checkForwardConflicts(conflicts mapStringSet) {
for _, pkg := range dp.Aur {
for _, conflict := range pkg.Conflicts {
dp.checkForwardConflict(pkg.Name, conflict, conflicts)
}
}
for _, pkg := range dp.Repo {
pkg.Conflicts().ForEach(func(conflict alpm.Depend) error {
dp.checkForwardConflict(pkg.Name(), conflict.String(), conflicts)
return nil
})
}
}
func (dp *depPool) checkReverseConflicts(conflicts mapStringSet) {
dp.LocalDb.PkgCache().ForEach(func(pkg alpm.Package) error {
pkg.Conflicts().ForEach(func(conflict alpm.Depend) error {
dp.checkReverseConflict(pkg.Name(), conflict.String(), conflicts)
return nil
})
return nil
})
}
func (dp *depPool) CheckConflicts() error {
var wg sync.WaitGroup
innerConflicts := make(mapStringSet)
conflicts := make(mapStringSet)
wg.Add(2)
fmt.Println(bold(cyan("::") + bold(" Checking for conflicts...")))
go func() {
dp.checkForwardConflicts(conflicts)
dp.checkReverseConflicts(conflicts)
wg.Done()
}()
fmt.Println(bold(cyan("::") + bold(" Checking for inner conflicts...")))
go func() {
dp.checkInnerConflicts(innerConflicts)
wg.Done()
}()
wg.Wait()
if len(innerConflicts) != 0 {
fmt.Println()
fmt.Println(bold(red(arrow)), bold("Inner conflicts found:"))
for name, pkgs := range innerConflicts {
str := red(bold(smallArrow)) + " " + name + ":"
for pkg := range pkgs {
str += " " + cyan(pkg) + ","
}
str = strings.TrimSuffix(str, ",")
fmt.Println(str)
}
return fmt.Errorf("Unresolvable package conflicts, aborting")
}
if len(conflicts) != 0 {
fmt.Println()
fmt.Println(bold(red(arrow)), bold("Package conflicts found:"))
for name, pkgs := range conflicts {
str := red(bold(smallArrow)) + " Installing " + cyan(name) + " will remove:"
for pkg := range pkgs {
str += " " + cyan(pkg) + ","
}
str = strings.TrimSuffix(str, ",")
fmt.Println(str)
}
fmt.Println()
}
return nil
}
type missing struct {
Good stringSet
Missing map[string][][]string
}
func (dp *depPool) _checkMissing(dep string, stack []string, missing *missing) {
if _, err := dp.LocalDb.PkgCache().FindSatisfier(dep); err == nil {
missing.Good.set(dep)
return
}
if missing.Good.get(dep) {
return
}
if trees, ok := missing.Missing[dep]; ok {
for _, tree := range trees {
if stringSliceEqual(tree, stack) {
return
}
}
missing.Missing[dep] = append(missing.Missing[dep], stack)
return
}
aurPkg := dp.findSatisfierAur(dep)
if aurPkg != nil {
missing.Good.set(dep)
for _, deps := range [3][]string{aurPkg.Depends, aurPkg.MakeDepends, aurPkg.CheckDepends} {
for _, aurDep := range deps {
dp._checkMissing(aurDep, append(stack, aurPkg.Name), missing)
}
}
return
}
repoPkg := dp.findSatisfierRepo(dep)
if repoPkg != nil {
missing.Good.set(dep)
repoPkg.Depends().ForEach(func(repoDep alpm.Depend) error {
dp._checkMissing(repoDep.String(), append(stack, repoPkg.Name()), missing)
return nil
})
return
}
missing.Missing[dep] = [][]string{stack}
}
func (dp *depPool) CheckMissing() error {
missing := &missing{
make(stringSet),
make(map[string][][]string),
}
for _, target := range dp.Targets {
dp._checkMissing(target.DepString(), make([]string, 0), missing)
}
if len(missing.Missing) == 0 {
return nil
}
fmt.Println(bold(red(arrow+" Error: ")) + "Could not find all required packages:")
for dep, trees := range missing.Missing {
for _, tree := range trees {
fmt.Print(" "+cyan(dep), " (Tree: ")
if len(tree) == 0 {
fmt.Print(cyan("Target "))
} else {
for _, pkg := range tree {
fmt.Print(cyan(pkg), " -> ")
}
}
fmt.Println(")")
}
}
return fmt.Errorf("")
}

138
depOrder.go Normal file
View file

@ -0,0 +1,138 @@
package main
import (
// "fmt"
"strconv"
// "strings"
// "sync"
alpm "github.com/jguer/go-alpm"
rpc "github.com/mikkeloscar/aur"
//gopkg "github.com/mikkeloscar/gopkgbuild"
)
type depOrder struct {
Aur []*rpc.Pkg
Repo []*alpm.Package
Missing []string
Runtime stringSet
}
func (do *depOrder) String() string {
str := ""
str += "\n" + red("Repo") + " (" + strconv.Itoa(len(do.Repo)) + ") :"
for _, pkg := range do.Repo {
if do.Runtime.get(pkg.Name()) {
str += " " + pkg.Name()
}
}
str += "\n" + red("Aur") + " (" + strconv.Itoa(len(do.Aur)) + ") :"
for _, pkg := range do.Aur {
if do.Runtime.get(pkg.Name) {
str += " " + pkg.Name
}
}
str += "\n" + red("Repo Make") + " (" + strconv.Itoa(len(do.Repo)) + ") :"
for _, pkg := range do.Repo {
if !do.Runtime.get(pkg.Name()) {
str += " " + pkg.Name()
}
}
str += "\n" + red("Aur Make") + " (" + strconv.Itoa(len(do.Aur)) + ") :"
for _, pkg := range do.Aur {
if !do.Runtime.get(pkg.Name) {
str += " " + pkg.Name
}
}
return str
}
func makeDepOrder() *depOrder {
return &depOrder{
make([]*rpc.Pkg, 0),
make([]*alpm.Package, 0),
make([]string, 0),
make(stringSet),
}
}
func getDepOrder(dp *depPool) *depOrder {
do := makeDepOrder()
for _, target := range dp.Targets {
dep := target.DepString()
aurPkg := dp.findSatisfierAur(dep)
if aurPkg != nil {
do.orderPkgAur(aurPkg, dp, true)
}
repoPkg := dp.findSatisfierRepo(dep)
if repoPkg != nil {
do.orderPkgRepo(repoPkg, dp, true)
}
}
return do
}
func (do *depOrder) orderPkgAur(pkg *rpc.Pkg, dp *depPool, runtime bool) {
if runtime {
do.Runtime.set(pkg.Name)
}
do.Aur = append(do.Aur, pkg)
delete(dp.Aur, pkg.Name)
for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} {
for _, dep := range deps {
aurPkg := dp.findSatisfierAur(dep)
if aurPkg != nil {
do.orderPkgAur(aurPkg, dp, runtime)
}
repoPkg := dp.findSatisfierRepo(dep)
if repoPkg != nil {
do.orderPkgRepo(repoPkg, dp, runtime)
}
runtime = false
}
}
}
func (do *depOrder) orderPkgRepo(pkg *alpm.Package, dp *depPool, runtime bool) {
if runtime {
do.Runtime.set(pkg.Name())
}
do.Repo = append(do.Repo, pkg)
delete(dp.Repo, pkg.Name())
pkg.Depends().ForEach(func(dep alpm.Depend) (err error) {
repoPkg := dp.findSatisfierRepo(dep.String())
if repoPkg != nil {
do.orderPkgRepo(repoPkg, dp, runtime)
}
return nil
})
}
func (do *depOrder) getMakeOnlyRepo() stringSet {
makeOnly := make(stringSet)
for _, pkg := range do.Repo {
if !do.Runtime.get(pkg.Name()) {
makeOnly.set(pkg.Name())
}
}
return makeOnly
}
func (do *depOrder) checkMissing() {
}

477
depPool.go Normal file
View file

@ -0,0 +1,477 @@
package main
import (
"fmt"
"strconv"
"strings"
"sync"
alpm "github.com/jguer/go-alpm"
rpc "github.com/mikkeloscar/aur"
//gopkg "github.com/mikkeloscar/gopkgbuild"
)
const PROVIDES = false
type target struct {
Db string
Name string
Mod string
Version string
}
func toTarget(pkg string) target {
db, dep := splitDbFromName(pkg)
name, mod, version := splitDep(dep)
return target{
db,
name,
mod,
version,
}
}
func (t target) DepString() string {
return t.Name + t.Mod + t.Version
}
func (t target) String() string {
if t.Db != "" {
return t.Db + "/" + t.DepString()
}
return t.DepString()
}
type depPool struct {
Targets []target
Repo map[string]*alpm.Package
Aur map[string]*rpc.Pkg
AurCache map[string]*rpc.Pkg
Groups []string
LocalDb *alpm.Db
SyncDb alpm.DbList
Warnings *aurWarnings
}
func makeDepPool() (*depPool, error) {
localDb, err := alpmHandle.LocalDb()
if err != nil {
return nil, err
}
syncDb, err := alpmHandle.SyncDbs()
if err != nil {
return nil, err
}
dp := &depPool{
make([]target, 0),
make(map[string]*alpm.Package),
make(map[string]*rpc.Pkg),
make(map[string]*rpc.Pkg),
make([]string, 0),
localDb,
syncDb,
&aurWarnings{},
}
return dp, nil
}
func (dp *depPool) String() string {
str := ""
str += "\n" + red("Targets") + " (" + strconv.Itoa(len(dp.Targets)) + ") :"
for _, pkg := range dp.Targets {
str += " " + pkg.String()
}
str += "\n" + red("Repo") + " (" + strconv.Itoa(len(dp.Repo)) + ") :"
for pkg := range dp.Repo {
str += " " + pkg
}
str += "\n" + red("Aur") + " (" + strconv.Itoa(len(dp.Aur)) + ") :"
for pkg := range dp.Aur {
str += " " + pkg
}
str += "\n" + red("Aur Cache") + " (" + strconv.Itoa(len(dp.AurCache)) + ") :"
for pkg := range dp.AurCache {
str += " " + pkg
}
str += "\n" + red("Groups") + " (" + strconv.Itoa(len(dp.Groups)) + ") :"
for _, pkg := range dp.Groups {
str += " " + pkg
}
return str
}
// Includes db/ prefixes and group installs
func (dp *depPool) ResolveTargets(pkgs []string) error {
for _, pkg := range pkgs {
target := toTarget(pkg)
dp.Targets = append(dp.Targets, target)
}
// RPC requests are slow
// Combine as many AUR package requests as possible into a single RPC
// call
aurTargets := make(stringSet)
var err error
//repo := make([]*alpm.Package, 0)
for _, target := range dp.Targets {
// skip targets already satisfied
// even if the user enters db/pkg and aur/pkg the latter will
// still get skiped even if it's from a different database to
// the one specified
// this is how pacman behaves
if dp.hasSatisfier(target.DepString()) {
fmt.Println("Skipping target", target)
continue
}
var foundPkg *alpm.Package
var singleDb *alpm.Db
// aur/ prefix means we only check the aur
if target.Db == "aur" {
aurTargets.set(target.DepString())
continue
}
// if theres a different priefix only look in that repo
if target.Db != "" {
singleDb, err = alpmHandle.SyncDbByName(target.Db)
if err != nil {
return err
}
foundPkg, err = singleDb.PkgCache().FindSatisfier(target.DepString())
//otherwise find it in any repo
} else {
foundPkg, err = dp.SyncDb.FindSatisfier(target.DepString())
}
if err == nil {
dp.ResolveRepoDependency(foundPkg)
continue
} else {
//check for groups
//currently we dont resolve the packages in a group
//only check if the group exists
//would be better to check the groups from singleDb if
//the user specified a db but theres no easy way to do
//it without making alpm_lists so dont bother for now
//db/group is probably a rare use case
_, err := dp.SyncDb.PkgCachebyGroup(target.Name)
if err == nil {
dp.Groups = append(dp.Groups, target.String())
continue
}
}
//if there was no db prefix check the aur
if target.Db == "" {
aurTargets.set(target.DepString())
}
}
if len(aurTargets) > 0 {
err = dp.resolveAURPackages(aurTargets)
}
return nil
}
// Pseudo provides finder.
// Try to find provides by performing a search of the package name
// This effectively performs -Ss on each package
// then runs -Si on each result to cache the information.
//
// For example if you were to -S yay then yay -Ss would give:
// yay-git yay-bin yay realyog pacui pacui-git ruby-yard
// These packages will all be added to the cache incase they are needed later
// Ofcouse only the first three packages provide yay, the rest are just false
// positives.
//
// This method increases dependency resolve time
func (dp *depPool) findProvides(pkgs stringSet) error {
var mux sync.Mutex
var wg sync.WaitGroup
doSearch := func(pkg string) {
defer wg.Done()
var err error
var results []rpc.Pkg
// Hack for a bigger search result, if the user wants
// java-envronment we can search for just java instead and get
// more hits.
words := strings.Split(pkg, "-")
for i := range words {
results, err = rpc.SearchByNameDesc(strings.Join(words[:i+1], "-"))
if err == nil {
break
}
}
if err != nil {
return
}
for _, result := range results {
mux.Lock()
if _, ok := dp.AurCache[result.Name]; !ok {
pkgs.set(result.Name)
}
mux.Unlock()
}
}
for pkg := range pkgs {
wg.Add(1)
go doSearch(pkg)
}
wg.Wait()
return nil
}
func (dp *depPool) cacheAURPackages(_pkgs stringSet) error {
pkgs := _pkgs.copy()
query := make([]string, 0)
for pkg := range pkgs {
if _, ok := dp.AurCache[pkg]; ok {
pkgs.remove(pkg)
}
}
if len(pkgs) == 0 {
return nil
}
//TODO: config option, maybe --deepsearh but aurman uses that flag for
//something else already which might be confusing
//maybe --provides
if PROVIDES {
err := dp.findProvides(pkgs)
if err != nil {
return err
}
}
for pkg := range pkgs {
if _, ok := dp.AurCache[pkg]; !ok {
name, _, _ := splitDep(pkg)
query = append(query, name)
}
}
info, err := aurInfo(query, dp.Warnings)
if err != nil {
return err
}
for _, pkg := range info {
// Dump everything in cache just in case we need it later
dp.AurCache[pkg.Name] = pkg
}
return nil
}
func (dp *depPool) resolveAURPackages(pkgs stringSet) error {
newPackages := make(stringSet)
newAURPackages := make(stringSet)
err := dp.cacheAURPackages(pkgs)
if err != nil {
return err
}
if len(pkgs) == 0 {
return nil
}
for name := range pkgs {
_, ok := dp.Aur[name]
if ok {
continue
}
pkg := dp.findSatisfierAurCache(name)
if pkg == nil {
continue
}
dp.Aur[pkg.Name] = pkg
for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} {
for _, dep := range deps {
newPackages.set(dep)
}
}
}
for dep := range newPackages {
if dp.hasSatisfier(dep) {
continue
}
//has satisfier installed: skip
_, isInstalled := dp.LocalDb.PkgCache().FindSatisfier(dep)
if isInstalled == nil {
continue
}
//has satisfier in repo: fetch it
repoPkg, inRepos := dp.SyncDb.FindSatisfier(dep)
if inRepos == nil {
dp.ResolveRepoDependency(repoPkg)
continue
}
//assume it's in the aur
//ditch the versioning because the RPC cant handle it
newAURPackages.set(dep)
}
err = dp.resolveAURPackages(newAURPackages)
return err
}
func (dp *depPool) ResolveRepoDependency(pkg *alpm.Package) {
dp.Repo[pkg.Name()] = pkg
pkg.Depends().ForEach(func(dep alpm.Depend) (err error) {
//have satisfier in dep tree: skip
if dp.hasSatisfier(dep.String()) {
return
}
//has satisfier installed: skip
_, isInstalled := dp.LocalDb.PkgCache().FindSatisfier(dep.String())
if isInstalled == nil {
return
}
//has satisfier in repo: fetch it
repoPkg, inRepos := dp.SyncDb.FindSatisfier(dep.String())
if inRepos != nil {
return
}
dp.ResolveRepoDependency(repoPkg)
return nil
})
}
func (dp *depPool) queryAUR(pkgs []string) error {
_, err := aurInfo(pkgs, dp.Warnings)
if err != nil {
return err
}
return nil
}
func getDepPool(pkgs []string) (*depPool, error) {
dp, err := makeDepPool()
if err != nil {
return nil, err
}
err = dp.ResolveTargets(pkgs)
return dp, err
}
func (dp *depPool) findSatisfierAur(dep string) *rpc.Pkg {
for _, pkg := range dp.Aur {
if satisfiesAur(dep, pkg) {
return pkg
}
}
return nil
}
// This is mostly used to promote packages from the cache
// to the Install list
// Provide a pacman style provider menu if theres more than one candidate
// TODO: maybe intermix repo providers in the menu
func (dp *depPool) findSatisfierAurCache(dep string) *rpc.Pkg {
//try to match providers
providers := make([]*rpc.Pkg, 0)
for _, pkg := range dp.AurCache {
if pkgSatisfies(pkg.Name, pkg.Version, dep) {
return pkg
}
}
for _, pkg := range dp.AurCache {
for _, provide := range pkg.Provides {
if provideSatisfies(provide, dep) {
providers = append(providers, pkg)
}
}
}
if len(providers) == 1 {
return providers[0]
}
if len(providers) > 1 {
return providerMenu(dep, providers)
}
return nil
}
func (dp *depPool) findSatisfierRepo(dep string) *alpm.Package {
for _, pkg := range dp.Repo {
if satisfiesRepo(dep, pkg) {
return pkg
}
}
return nil
}
func (dp *depPool) hasSatisfier(dep string) bool {
return dp.findSatisfierRepo(dep) != nil || dp.findSatisfierAur(dep) != nil
}
func (dp *depPool) hasPackage(name string) bool {
for _, pkg := range dp.Repo {
if pkg.Name() == name {
return true
}
}
for _, pkg := range dp.Aur {
if pkg.Name == name {
return true
}
}
for _, pkg := range dp.Groups {
if pkg == name {
return true
}
}
return false
}

View file

@ -41,6 +41,16 @@ func (set stringSet) toSlice() []string {
return slice
}
func (set stringSet) copy() stringSet {
newSet := make(stringSet)
for str := range set {
newSet.set(str)
}
return newSet
}
func sliceToStringSet(in []string) stringSet {
set := make(stringSet)

View file

@ -1,6 +1,7 @@
package main
import (
"bufio"
"bytes"
"encoding/xml"
"fmt"
@ -566,3 +567,61 @@ func colourHash(name string) (output string) {
}
return fmt.Sprintf("\x1b[%dm%s\x1b[0m", hash%6+31, name)
}
func providerMenu(dep string, providers []*rpc.Pkg) *rpc.Pkg {
size := len(providers)
fmt.Print(bold(cyan(":: ")))
str := bold(fmt.Sprintf(bold("There are %d providers available for %s:"), size, dep))
size = 1
str += bold(cyan("\n:: ")) + bold("Repository AUR\n ")
for _, pkg := range providers {
str += fmt.Sprintf("%d) %s ", size, pkg.Name)
size++
}
fmt.Println(str)
for {
fmt.Print("\nEnter a number (default=1): ")
if config.NoConfirm {
fmt.Println()
break
}
reader := bufio.NewReader(os.Stdin)
numberBuf, overflow, err := reader.ReadLine()
if err != nil {
fmt.Println(err)
break
}
if overflow {
fmt.Println("Input too long")
continue
}
if string(numberBuf) == "" {
return providers[0]
}
num, err := strconv.Atoi(string(numberBuf))
if err != nil {
fmt.Printf("%s invalid number: %s\n", red("error:"), string(numberBuf))
continue
}
if num < 1 || num > size {
fmt.Printf("%s invalid value: %d is not between %d and %d\n", red("error:"), num, 1, size)
continue
}
return providers[num-1]
}
return nil
}

View file

@ -112,3 +112,25 @@ func lessRunes(iRunes, jRunes []rune) bool {
return len(iRunes) < len(jRunes)
}
func stringSliceEqual(a, b []string) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}