first commit
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user