145 lines
4.6 KiB
Go
145 lines
4.6 KiB
Go
package lexicomai
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// distroNameRe — имя дистрибутива в репозитории: <product>-<version>-<branch>.sh
|
|
// (например ups-7.012-release.sh, ses-7.045-beta.sh). Версия может содержать
|
|
// точки, ветка — release|beta.
|
|
var distroNameRe = regexp.MustCompile(`^(?P<product>[A-Za-z0-9_-]+)-(?P<version>[0-9][0-9.]*)-(?P<branch>release|beta)\.sh$`)
|
|
|
|
// Version — семантическая версия (разбивка по точкам). Сравнение числовое,
|
|
// поэтому 7.12 > 7.9 (в отличие от лексикографического).
|
|
type Version []int
|
|
|
|
// ParseVersion разбирает строку версии "7.012" в числовые сегменты.
|
|
// Нечисловой сегмент обрезается (считаем по корректному префиксу).
|
|
func ParseVersion(s string) Version {
|
|
parts := strings.Split(s, ".")
|
|
v := make(Version, 0, len(parts))
|
|
for _, p := range parts {
|
|
n, err := strconv.Atoi(strings.TrimSpace(p))
|
|
if err != nil {
|
|
break
|
|
}
|
|
v = append(v, n)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// String возвращает канонический вид версии (сегменты через точку).
|
|
func (v Version) String() string {
|
|
s := make([]string, len(v))
|
|
for i, n := range v {
|
|
s[i] = strconv.Itoa(n)
|
|
}
|
|
return strings.Join(s, ".")
|
|
}
|
|
|
|
// Compare сравнивает две версии семантически.
|
|
func CompareVersions(a, b string) int {
|
|
return ParseVersion(a).Compare(ParseVersion(b))
|
|
}
|
|
|
|
// Compare сравнивает две версии посегментно (короткая версия считается меньшей
|
|
// при равенстве общих сегментов, что соответствует общепринятой семантике).
|
|
func (v Version) Compare(o Version) int {
|
|
n := len(v)
|
|
if len(o) < n {
|
|
n = len(o)
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
if v[i] < o[i] {
|
|
return -1
|
|
}
|
|
if v[i] > o[i] {
|
|
return 1
|
|
}
|
|
}
|
|
switch {
|
|
case len(v) < len(o):
|
|
return -1
|
|
case len(v) > len(o):
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// distroParts — разобранное имя дистрибутива.
|
|
type distroParts struct {
|
|
Product string
|
|
Version string
|
|
Branch string
|
|
}
|
|
|
|
// parseDistroName разбирает имя файла дистрибутива.
|
|
func parseDistroName(name string) (distroParts, error) {
|
|
m := distroNameRe.FindStringSubmatch(name)
|
|
if m == nil {
|
|
return distroParts{}, fmt.Errorf("not a distro name: %q", name)
|
|
}
|
|
return distroParts{
|
|
Product: m[1],
|
|
Version: m[2],
|
|
Branch: m[3],
|
|
}, nil
|
|
}
|
|
|
|
// ResolveDistro выбирает имя файла дистрибутива из списка кандидатов.
|
|
//
|
|
// product — продукт; version — требуемая версия (пустая = последняя в ветке);
|
|
// branch — release|beta (по умолчанию release). Возвращает имя файла.
|
|
// Если по условиям кандидатов нет — доменная ошибка (модель может исправить).
|
|
func ResolveDistro(names []string, product, version, branch string) (string, error) {
|
|
if branch == "" {
|
|
branch = "release"
|
|
}
|
|
// Собираем кандидаты: продукт + ветка, отсортированные по убыванию версии.
|
|
var candidates []distroParts
|
|
for _, n := range names {
|
|
p, err := parseDistroName(n)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if p.Product == product && p.Branch == branch {
|
|
candidates = append(candidates, p)
|
|
}
|
|
}
|
|
if len(candidates) == 0 {
|
|
return "", fmt.Errorf("no %s distro in %s for %q", product, branch, product)
|
|
}
|
|
sort.SliceStable(candidates, func(i, j int) bool {
|
|
return CompareVersions(candidates[i].Version, candidates[j].Version) > 0
|
|
})
|
|
|
|
if version != "" {
|
|
for _, c := range candidates {
|
|
if c.Version == version {
|
|
return product + "-" + c.Version + "-" + c.Branch + ".sh", nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("version %q of %s not found in %s", version, product, branch)
|
|
}
|
|
top := candidates[0]
|
|
return product + "-" + top.Version + "-" + top.Branch + ".sh", nil
|
|
}
|
|
|
|
// normalizeBranch приводит значение ветки к каноническому виду (release/beta).
|
|
func normalizeBranch(branch string) (string, error) {
|
|
branch = strings.ToLower(strings.TrimSpace(branch))
|
|
switch branch {
|
|
case "", "release":
|
|
return "release", nil
|
|
case "beta":
|
|
return "beta", nil
|
|
default:
|
|
return "", fmt.Errorf("unknown branch %q (expected release or beta)", branch)
|
|
}
|
|
}
|