aboutsummaryrefslogtreecommitdiff
path: root/internal/config
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-26 05:13:25 +0000
committerFuwn <[email protected]>2026-01-26 05:13:25 +0000
commit567e5b51d32450999935bcf3af143248888e12e9 (patch)
tree41bb8b2a8fcae168bcb7044adb84f08dc89aaec0 /internal/config
downloadmugi-567e5b51d32450999935bcf3af143248888e12e9.tar.xz
mugi-567e5b51d32450999935bcf3af143248888e12e9.zip
feat: Initial commit
Diffstat (limited to 'internal/config')
-rw-r--r--internal/config/config.go250
1 files changed, 250 insertions, 0 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
new file mode 100644
index 0000000..9564b4e
--- /dev/null
+++ b/internal/config/config.go
@@ -0,0 +1,250 @@
+package config
+
+import (
+ "os"
+ "path/filepath"
+ "slices"
+ "strings"
+
+ "gopkg.in/yaml.v3"
+)
+
+type RemoteDefinition struct {
+ Aliases []string `yaml:"aliases"`
+ URL string `yaml:"url"`
+}
+
+type Defaults struct {
+ Remotes []string `yaml:"remotes"`
+ PathPrefix string `yaml:"path_prefix"`
+}
+
+type RepoRemotes map[string]string
+
+type Repo struct {
+ Path string
+ Remotes RepoRemotes
+}
+
+type Config struct {
+ Remotes map[string]RemoteDefinition
+ Defaults Defaults
+ Repos map[string]Repo
+}
+
+type rawConfig struct {
+ Remotes map[string]RemoteDefinition `yaml:"remotes"`
+ Defaults Defaults `yaml:"defaults"`
+ Repos map[string]yaml.Node `yaml:"repos"`
+}
+
+type remoteOverride struct {
+ User string `yaml:"user"`
+ Repo string `yaml:"repo"`
+}
+
+func Load(override string) (Config, error) {
+ path := override
+
+ if path == "" {
+ var err error
+
+ path, err = Path()
+ if err != nil {
+ return Config{}, err
+ }
+ }
+
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return Config{}, err
+ }
+
+ var raw rawConfig
+
+ if err := yaml.Unmarshal(data, &raw); err != nil {
+ return Config{}, err
+ }
+
+ return expand(raw)
+}
+
+func expand(raw rawConfig) (Config, error) {
+ cfg := Config{
+ Remotes: raw.Remotes,
+ Defaults: raw.Defaults,
+ Repos: make(map[string]Repo),
+ }
+
+ for name, node := range raw.Repos {
+ repo, err := expandRepo(name, node, raw)
+ if err != nil {
+ return Config{}, err
+ }
+
+ cfg.Repos[name] = repo
+ }
+
+ return cfg, nil
+}
+
+func expandRepo(name string, node yaml.Node, raw rawConfig) (Repo, error) {
+ user, repoName := splitRepoName(name)
+ repo := Repo{
+ Remotes: make(RepoRemotes),
+ }
+
+ var parsed map[string]yaml.Node
+
+ if err := node.Decode(&parsed); err != nil {
+ parsed = make(map[string]yaml.Node)
+ }
+
+ if pathNode, ok := parsed["path"]; ok {
+ var path string
+
+ pathNode.Decode(&path)
+
+ repo.Path = path
+ } else if raw.Defaults.PathPrefix != "" {
+ repo.Path = filepath.Join(raw.Defaults.PathPrefix, repoName)
+ }
+
+ remoteList := raw.Defaults.Remotes
+
+ if remotesNode, ok := parsed["remotes"]; ok {
+ var list []string
+
+ if err := remotesNode.Decode(&list); err == nil {
+ remoteList = list
+ } else {
+ var oldStyle map[string]string
+
+ if err := remotesNode.Decode(&oldStyle); err == nil {
+ repo.Remotes = oldStyle
+
+ return repo, nil
+ }
+ }
+ }
+
+ for _, remoteName := range remoteList {
+ remoteUser, remoteRepo := user, repoName
+
+ if overrideNode, ok := parsed[remoteName]; ok {
+ var override remoteOverride
+
+ if err := overrideNode.Decode(&override); err == nil {
+ if override.User != "" {
+ remoteUser = override.User
+ }
+
+ if override.Repo != "" {
+ remoteRepo = override.Repo
+ }
+ } else {
+ var urlOverride string
+
+ if err := overrideNode.Decode(&urlOverride); err == nil {
+ repo.Remotes[remoteName] = urlOverride
+
+ continue
+ }
+ }
+ }
+
+ if def, ok := raw.Remotes[remoteName]; ok && def.URL != "" {
+ url := expandURL(def.URL, remoteUser, remoteRepo)
+
+ repo.Remotes[remoteName] = url
+ }
+ }
+
+ return repo, nil
+}
+
+func expandURL(template, user, repo string) string {
+ url := strings.ReplaceAll(template, "${user}", user)
+ url = strings.ReplaceAll(url, "${repo}", repo)
+
+ return url
+}
+
+func splitRepoName(name string) (user, repo string) {
+ parts := strings.SplitN(name, "/", 2)
+
+ if len(parts) == 2 {
+ return parts[0], parts[1]
+ }
+
+ return "", parts[0]
+}
+
+func Path() (string, error) {
+ configDir := os.Getenv("XDG_CONFIG_HOME")
+
+ if configDir == "" {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "", err
+ }
+
+ configDir = filepath.Join(home, ".config")
+ }
+
+ return filepath.Join(configDir, "mugi", "config.yaml"), nil
+}
+
+func (c Config) FindRepo(name string) (string, Repo, bool) {
+ if repo, ok := c.Repos[name]; ok {
+ return name, repo, true
+ }
+
+ var matches []string
+
+ for fullName := range c.Repos {
+ repoName := filepath.Base(fullName)
+
+ if repoName == name {
+ matches = append(matches, fullName)
+ }
+ }
+
+ if len(matches) == 1 {
+ return matches[0], c.Repos[matches[0]], true
+ }
+
+ return "", Repo{}, false
+}
+
+func (c Config) AllRepos() []string {
+ repos := make([]string, 0, len(c.Repos))
+
+ for name := range c.Repos {
+ repos = append(repos, name)
+ }
+
+ return repos
+}
+
+func (c Config) ResolveAlias(alias string) string {
+ for name, def := range c.Remotes {
+ if name == alias || slices.Contains(def.Aliases, alias) {
+ return name
+ }
+ }
+
+ return alias
+}
+
+func (r Repo) ExpandPath() string {
+ path := r.Path
+
+ if len(path) > 0 && path[0] == '~' {
+ if home, err := os.UserHomeDir(); err == nil {
+ path = filepath.Join(home, path[1:])
+ }
+ }
+
+ return path
+}