diff options
| author | Fuwn <[email protected]> | 2026-01-26 05:13:25 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-01-26 05:13:25 +0000 |
| commit | 567e5b51d32450999935bcf3af143248888e12e9 (patch) | |
| tree | 41bb8b2a8fcae168bcb7044adb84f08dc89aaec0 /internal/config | |
| download | mugi-567e5b51d32450999935bcf3af143248888e12e9.tar.xz mugi-567e5b51d32450999935bcf3af143248888e12e9.zip | |
feat: Initial commit
Diffstat (limited to 'internal/config')
| -rw-r--r-- | internal/config/config.go | 250 |
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 +} |