master
  1package main
  2
  3import (
  4	"fmt"
  5	"os"
  6	"os/exec"
  7	"path/filepath"
  8	"strings"
  9
 10	"github.com/BurntSushi/toml"
 11)
 12
 13type SingleRepoConfig struct {
 14	Name          string `toml:"name"`
 15	Path          string `toml:"path"`
 16	Description   string `toml:"description"`
 17	DefaultBranch string `toml:"default_branch"`
 18}
 19
 20type RepoEntry struct {
 21	Name          string `toml:"name"`
 22	Slug          string `toml:"slug"`
 23	Path          string `toml:"path"`
 24	Description   string `toml:"description"`
 25	DefaultBranch string `toml:"default_branch"`
 26}
 27
 28type Config struct {
 29	SiteName     string            `toml:"site_name"`
 30	Repo         *SingleRepoConfig `toml:"repo"`
 31	Repos        []RepoEntry       `toml:"repos"`
 32	Sort         string            `toml:"sort"`
 33	Theme        string            `toml:"theme"`
 34	ThemeLight   string            `toml:"theme_light"`
 35	ThemeDark    string            `toml:"theme_dark"`
 36	Branches     string            `toml:"branches"`
 37	Output       string            `toml:"output"`
 38	Minify       bool              `toml:"minify"`
 39	Gzip         bool              `toml:"gzip"`
 40	Git          bool              `toml:"git"`
 41	InlineStyles bool              `toml:"inline_styles"`
 42}
 43
 44func parseConfig(path string) (*Config, error) {
 45	absPath, err := filepath.Abs(path)
 46	if err != nil {
 47		return nil, fmt.Errorf("resolve config path: %w", err)
 48	}
 49
 50	data, err := os.ReadFile(absPath)
 51	if err != nil {
 52		return nil, fmt.Errorf("read config file: %w", err)
 53	}
 54
 55	var cfg Config
 56	if err := toml.Unmarshal(data, &cfg); err != nil {
 57		return nil, fmt.Errorf("parse config: %w", err)
 58	}
 59
 60	if cfg.SiteName == "" {
 61		cfg.SiteName = "Git repositories"
 62	}
 63
 64	if hasRepo := cfg.Repo != nil; hasRepo {
 65		if len(cfg.Repos) > 0 {
 66			return nil, fmt.Errorf("config: cannot use both [repo] and [[repos]]")
 67		}
 68		if cfg.Repo.Name == "" {
 69			return nil, fmt.Errorf("config: [repo].name is required")
 70		}
 71		if cfg.Repo.Path == "" {
 72			return nil, fmt.Errorf("config: [repo].path is required")
 73		}
 74		if err := normalizeRepoPath(&cfg.Repo.Path); err != nil {
 75			return nil, fmt.Errorf("config: [repo].path: %w", err)
 76		}
 77		if cfg.Repo.DefaultBranch == "" {
 78			cfg.Repo.DefaultBranch = autoDefaultBranchName(cfg.Repo.Path)
 79		}
 80	}
 81
 82	if len(cfg.Repos) > 0 {
 83		for i := range cfg.Repos {
 84			repo := &cfg.Repos[i]
 85			if repo.Name == "" {
 86				return nil, fmt.Errorf("config: repos[%d].name is required", i)
 87			}
 88			if repo.Slug == "" {
 89				return nil, fmt.Errorf("config: repos[%d].slug is required", i)
 90			}
 91			if repo.Path == "" {
 92				return nil, fmt.Errorf("config: repos[%d].path is required", i)
 93			}
 94			if err := normalizeRepoPath(&repo.Path); err != nil {
 95				return nil, fmt.Errorf("config: repos[%d].path: %w", i, err)
 96			}
 97			if repo.DefaultBranch == "" {
 98				repo.DefaultBranch = autoDefaultBranchName(repo.Path)
 99			}
100		}
101	}
102
103	return &cfg, nil
104}
105
106func normalizeRepoPath(path *string) error {
107	abs, err := filepath.Abs(*path)
108	if err != nil {
109		return err
110	}
111	*path = abs
112	return nil
113}
114
115func autoDefaultBranchName(repoPath string) string {
116	cmd := exec.Command("git", "symbolic-ref", "--short", "HEAD")
117	cmd.Dir = repoPath
118	out, err := cmd.Output()
119	if err != nil {
120		return ""
121	}
122	return strings.TrimSpace(string(out))
123}