mirror of
https://github.com/webinstall/webi-installers.git
synced 2026-04-07 02:46:50 +00:00
ref(installerconf): use typed struct instead of string map
Conf is now a plain struct with typed fields (Source, Owner, Repo, TagPrefix, VersionPrefix, Exclude, BaseURL) instead of a generic map[string]string with accessor methods. Unrecognized keys go into an Extra map for forward compatibility. Config stays flat key=value — covers the common patterns (simple github, version prefix stripping, monorepo tag prefix, filename exclusions). Complex cases belong in Go code, not config.
This commit is contained in:
@@ -3,9 +3,46 @@
|
||||
// The format is simple key=value, one per line. Blank lines and lines
|
||||
// starting with # are ignored. Keys and values are trimmed of whitespace.
|
||||
//
|
||||
// Minimal example (covers ~60% of packages):
|
||||
//
|
||||
// source = github
|
||||
// owner = sharkdp
|
||||
// repo = bat
|
||||
//
|
||||
// With version prefix stripping (jq tags are "jq-1.7.1"):
|
||||
//
|
||||
// source = github
|
||||
// owner = jqlang
|
||||
// repo = jq
|
||||
// version_prefix = jq-
|
||||
//
|
||||
// With filename exclusions (hugo publishes _extended_ variants):
|
||||
//
|
||||
// source = github
|
||||
// owner = gohugoio
|
||||
// repo = hugo
|
||||
// exclude = _extended_, Linux-64bit
|
||||
//
|
||||
// Monorepo with tag prefix:
|
||||
//
|
||||
// source = github
|
||||
// owner = therootcompany
|
||||
// repo = golib
|
||||
// tag_prefix = tools/monorel/
|
||||
//
|
||||
// Non-GitHub sources:
|
||||
//
|
||||
// source = nodedist
|
||||
// url = https://nodejs.org/download/release
|
||||
//
|
||||
// source = gitea
|
||||
// base_url = https://gitea.com
|
||||
// owner = xorm
|
||||
// repo = xorm
|
||||
//
|
||||
// Complex packages that need custom logic beyond what the classifier
|
||||
// auto-detects (e.g. ollama's universal binaries, ffmpeg's non-standard
|
||||
// naming) should put that logic in Go code, not in the config.
|
||||
package installerconf
|
||||
|
||||
import (
|
||||
@@ -15,9 +52,38 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Conf holds the parsed contents of a releases.conf file.
|
||||
// Conf holds the parsed per-package release configuration.
|
||||
type Conf struct {
|
||||
m map[string]string
|
||||
// Source is the fetch source type: "github", "gitea", "gitlab",
|
||||
// "gittag", "nodedist", etc.
|
||||
Source string
|
||||
|
||||
// Owner is the repository owner (org or user).
|
||||
Owner string
|
||||
|
||||
// Repo is the repository name.
|
||||
Repo string
|
||||
|
||||
// BaseURL is a custom base URL for non-GitHub sources
|
||||
// (e.g. a Gitea instance or nodedist index URL).
|
||||
BaseURL string
|
||||
|
||||
// TagPrefix filters releases in monorepos. Only tags starting with
|
||||
// this prefix are included, and the prefix is stripped from the
|
||||
// version string. Example: "tools/monorel/"
|
||||
TagPrefix string
|
||||
|
||||
// VersionPrefix is stripped from version strings.
|
||||
// Example: jq tags as "jq-1.7.1" → set to "jq-" to get "1.7.1".
|
||||
VersionPrefix string
|
||||
|
||||
// Exclude lists filename substrings to filter out.
|
||||
// Assets whose name contains any of these are skipped.
|
||||
// Example: ["_extended_", "-gogit-", "-docs-"]
|
||||
Exclude []string
|
||||
|
||||
// Extra holds any unrecognized keys for forward compatibility.
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
// Read parses a releases.conf file.
|
||||
@@ -28,7 +94,7 @@ func Read(path string) (*Conf, error) {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
c := &Conf{m: make(map[string]string)}
|
||||
raw := make(map[string]string)
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
@@ -39,20 +105,49 @@ func Read(path string) (*Conf, error) {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
c.m[strings.TrimSpace(key)] = strings.TrimSpace(val)
|
||||
raw[strings.TrimSpace(key)] = strings.TrimSpace(val)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("installerconf: read %s: %w", path, err)
|
||||
}
|
||||
|
||||
c := &Conf{}
|
||||
c.Source = raw["source"]
|
||||
c.Owner = raw["owner"]
|
||||
c.Repo = raw["repo"]
|
||||
c.TagPrefix = raw["tag_prefix"]
|
||||
c.VersionPrefix = raw["version_prefix"]
|
||||
|
||||
if v := raw["base_url"]; v != "" {
|
||||
c.BaseURL = v
|
||||
} else {
|
||||
c.BaseURL = raw["url"]
|
||||
}
|
||||
|
||||
if v := raw["exclude"]; v != "" {
|
||||
for _, p := range strings.Split(v, ",") {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
c.Exclude = append(c.Exclude, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect unrecognized keys.
|
||||
known := map[string]bool{
|
||||
"source": true, "owner": true, "repo": true,
|
||||
"base_url": true, "url": true,
|
||||
"tag_prefix": true, "version_prefix": true,
|
||||
"exclude": true,
|
||||
}
|
||||
for k, v := range raw {
|
||||
if !known[k] {
|
||||
if c.Extra == nil {
|
||||
c.Extra = make(map[string]string)
|
||||
}
|
||||
c.Extra[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Get returns the value for a key, or "" if not set.
|
||||
func (c *Conf) Get(key string) string {
|
||||
return c.m[key]
|
||||
}
|
||||
|
||||
// Source returns the fetch source type (e.g. "github", "nodedist", "gitea").
|
||||
func (c *Conf) Source() string {
|
||||
return c.m["source"]
|
||||
}
|
||||
|
||||
@@ -8,106 +8,128 @@ import (
|
||||
"github.com/webinstall/webi-installers/internal/installerconf"
|
||||
)
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "releases.conf")
|
||||
os.WriteFile(path, []byte(`
|
||||
# Hugo release config
|
||||
func TestSimpleGitHub(t *testing.T) {
|
||||
c := confFromString(t, `
|
||||
source = github
|
||||
owner = sharkdp
|
||||
repo = bat
|
||||
`)
|
||||
assertEqual(t, "Source", c.Source, "github")
|
||||
assertEqual(t, "Owner", c.Owner, "sharkdp")
|
||||
assertEqual(t, "Repo", c.Repo, "bat")
|
||||
assertEqual(t, "TagPrefix", c.TagPrefix, "")
|
||||
assertEqual(t, "VersionPrefix", c.VersionPrefix, "")
|
||||
|
||||
if len(c.Exclude) != 0 {
|
||||
t.Errorf("Exclude = %v, want empty", c.Exclude)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionPrefix(t *testing.T) {
|
||||
c := confFromString(t, `
|
||||
source = github
|
||||
owner = jqlang
|
||||
repo = jq
|
||||
version_prefix = jq-
|
||||
`)
|
||||
assertEqual(t, "VersionPrefix", c.VersionPrefix, "jq-")
|
||||
}
|
||||
|
||||
func TestExclude(t *testing.T) {
|
||||
c := confFromString(t, `
|
||||
source = github
|
||||
owner = gohugoio
|
||||
repo = hugo
|
||||
`), 0o644)
|
||||
|
||||
c, err := installerconf.Read(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Source() != "github" {
|
||||
t.Errorf("Source() = %q, want github", c.Source())
|
||||
}
|
||||
if c.Get("owner") != "gohugoio" {
|
||||
t.Errorf("owner = %q, want gohugoio", c.Get("owner"))
|
||||
}
|
||||
if c.Get("repo") != "hugo" {
|
||||
t.Errorf("repo = %q, want hugo", c.Get("repo"))
|
||||
exclude = _extended_, Linux-64bit
|
||||
`)
|
||||
if len(c.Exclude) != 2 {
|
||||
t.Fatalf("Exclude has %d items, want 2: %v", len(c.Exclude), c.Exclude)
|
||||
}
|
||||
assertEqual(t, "Exclude[0]", c.Exclude[0], "_extended_")
|
||||
assertEqual(t, "Exclude[1]", c.Exclude[1], "Linux-64bit")
|
||||
}
|
||||
|
||||
func TestReadMonorepo(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "releases.conf")
|
||||
os.WriteFile(path, []byte(`source = github
|
||||
func TestMonorepoTagPrefix(t *testing.T) {
|
||||
c := confFromString(t, `
|
||||
source = github
|
||||
owner = therootcompany
|
||||
repo = golib
|
||||
tag_prefix = tools/monorel/
|
||||
`), 0o644)
|
||||
|
||||
c, err := installerconf.Read(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Get("tag_prefix") != "tools/monorel/" {
|
||||
t.Errorf("tag_prefix = %q", c.Get("tag_prefix"))
|
||||
}
|
||||
`)
|
||||
assertEqual(t, "TagPrefix", c.TagPrefix, "tools/monorel/")
|
||||
}
|
||||
|
||||
func TestReadNodeDist(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "releases.conf")
|
||||
os.WriteFile(path, []byte(`source = nodedist
|
||||
func TestNodeDist(t *testing.T) {
|
||||
c := confFromString(t, `
|
||||
source = nodedist
|
||||
url = https://nodejs.org/download/release
|
||||
`), 0o644)
|
||||
|
||||
c, err := installerconf.Read(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Source() != "nodedist" {
|
||||
t.Errorf("Source() = %q, want nodedist", c.Source())
|
||||
}
|
||||
if c.Get("url") != "https://nodejs.org/download/release" {
|
||||
t.Errorf("url = %q", c.Get("url"))
|
||||
}
|
||||
`)
|
||||
assertEqual(t, "Source", c.Source, "nodedist")
|
||||
assertEqual(t, "BaseURL", c.BaseURL, "https://nodejs.org/download/release")
|
||||
}
|
||||
|
||||
func TestReadSkipsBlanksAndComments(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "releases.conf")
|
||||
os.WriteFile(path, []byte(`
|
||||
# comment
|
||||
func TestGiteaBaseURL(t *testing.T) {
|
||||
c := confFromString(t, `
|
||||
source = gitea
|
||||
base_url = https://gitea.com
|
||||
owner = xorm
|
||||
repo = xorm
|
||||
`)
|
||||
assertEqual(t, "Source", c.Source, "gitea")
|
||||
assertEqual(t, "BaseURL", c.BaseURL, "https://gitea.com")
|
||||
assertEqual(t, "Owner", c.Owner, "xorm")
|
||||
}
|
||||
|
||||
func TestBlanksAndComments(t *testing.T) {
|
||||
c := confFromString(t, `
|
||||
# Hugo config
|
||||
source = github
|
||||
|
||||
# another comment
|
||||
# owner line
|
||||
owner = foo
|
||||
`), 0o644)
|
||||
`)
|
||||
assertEqual(t, "Source", c.Source, "github")
|
||||
assertEqual(t, "Owner", c.Owner, "foo")
|
||||
}
|
||||
|
||||
c, err := installerconf.Read(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Source() != "github" {
|
||||
t.Errorf("Source() = %q", c.Source())
|
||||
}
|
||||
if c.Get("owner") != "foo" {
|
||||
t.Errorf("owner = %q", c.Get("owner"))
|
||||
func TestExtraKeys(t *testing.T) {
|
||||
c := confFromString(t, `
|
||||
source = github
|
||||
owner = foo
|
||||
repo = bar
|
||||
custom_thing = hello
|
||||
`)
|
||||
if c.Extra == nil || c.Extra["custom_thing"] != "hello" {
|
||||
t.Errorf("Extra[custom_thing] = %q, want hello", c.Extra["custom_thing"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMissing(t *testing.T) {
|
||||
func TestEmptyExclude(t *testing.T) {
|
||||
c := confFromString(t, "source = github\n")
|
||||
if c.Exclude != nil {
|
||||
t.Errorf("Exclude = %v, want nil", c.Exclude)
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
func confFromString(t *testing.T, content string) *installerconf.Conf {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "releases.conf")
|
||||
os.WriteFile(path, []byte("source = github\n"), 0o644)
|
||||
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := installerconf.Read(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
if c.Get("nonexistent") != "" {
|
||||
t.Errorf("Get(nonexistent) = %q, want empty", c.Get("nonexistent"))
|
||||
func assertEqual(t *testing.T, name, got, want string) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Errorf("%s = %q, want %q", name, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user