Commit 8a37f7f

HPCesia <me@hpcesia.com>
2026-07-01 18:58:44
Add TOML config types, parsing, and --config flag
1 parent 122a639
config.go
@@ -0,0 +1,123 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/BurntSushi/toml"
+)
+
+type SingleRepoConfig struct {
+	Name          string `toml:"name"`
+	Path          string `toml:"path"`
+	Description   string `toml:"description"`
+	DefaultBranch string `toml:"default_branch"`
+}
+
+type RepoEntry struct {
+	Name          string `toml:"name"`
+	Slug          string `toml:"slug"`
+	Path          string `toml:"path"`
+	Description   string `toml:"description"`
+	DefaultBranch string `toml:"default_branch"`
+}
+
+type Config struct {
+	SiteName string            `toml:"site_name"`
+	Repo     *SingleRepoConfig `toml:"repo"`
+	Repos    []RepoEntry       `toml:"repos"`
+}
+
+func parseConfig(path string) (*Config, error) {
+	absPath, err := filepath.Abs(path)
+	if err != nil {
+		return nil, fmt.Errorf("resolve config path: %w", err)
+	}
+
+	data, err := os.ReadFile(absPath)
+	if err != nil {
+		return nil, fmt.Errorf("read config file: %w", err)
+	}
+
+	var cfg Config
+	if err := toml.Unmarshal(data, &cfg); err != nil {
+		return nil, fmt.Errorf("parse config: %w", err)
+	}
+
+	if cfg.SiteName == "" {
+		cfg.SiteName = "Git repositories"
+	}
+
+	if hasRepo := cfg.Repo != nil; hasRepo {
+		if len(cfg.Repos) > 0 {
+			return nil, fmt.Errorf("config: cannot use both [repo] and [[repos]]")
+		}
+		if cfg.Repo.Name == "" {
+			return nil, fmt.Errorf("config: [repo].name is required")
+		}
+		if cfg.Repo.Path == "" {
+			return nil, fmt.Errorf("config: [repo].path is required")
+		}
+		if err := normalizeRepoPath(&cfg.Repo.Path); err != nil {
+			return nil, fmt.Errorf("config: [repo].path: %w", err)
+		}
+		if cfg.Repo.DefaultBranch == "" {
+			cfg.Repo.DefaultBranch = autoDefaultBranchName(cfg.Repo.Path)
+		}
+	}
+
+	if len(cfg.Repos) > 0 {
+		for i := range cfg.Repos {
+			repo := &cfg.Repos[i]
+			if repo.Name == "" {
+				return nil, fmt.Errorf("config: repos[%d].name is required", i)
+			}
+			if repo.Slug == "" {
+				return nil, fmt.Errorf("config: repos[%d].slug is required", i)
+			}
+			if repo.Path == "" {
+				return nil, fmt.Errorf("config: repos[%d].path is required", i)
+			}
+			if err := normalizeRepoPath(&repo.Path); err != nil {
+				return nil, fmt.Errorf("config: repos[%d].path: %w", i, err)
+			}
+			if repo.DefaultBranch == "" {
+				repo.DefaultBranch = autoDefaultBranchName(repo.Path)
+			}
+		}
+	}
+
+	return &cfg, nil
+}
+
+func normalizeRepoPath(path *string) error {
+	abs, err := filepath.Abs(*path)
+	if err != nil {
+		return err
+	}
+	*path = abs
+	return nil
+}
+
+func autoDefaultBranchName(repoPath string) string {
+	candidates := []string{"master", "main"}
+	for _, name := range candidates {
+		refPath := filepath.Join(repoPath, ".git", "refs", "heads", name)
+		if _, err := os.Stat(refPath); err == nil {
+			return name
+		}
+	}
+	return ""
+}
+
+func sanitizeSlug(name string) string {
+	return strings.NewReplacer(
+		" ", "-",
+		"_", "-",
+		"/", "-",
+		"\\", "-",
+		".", "-",
+	).Replace(strings.ToLower(name))
+}
go.mod
@@ -3,6 +3,7 @@ module github.com/antonmedv/gitmal
 go 1.24.0
 
 require (
+	github.com/BurntSushi/toml v1.6.0
 	github.com/alecthomas/chroma/v2 v2.20.0
 	github.com/spf13/pflag v1.0.10
 	github.com/tdewolff/minify/v2 v2.24.7
go.sum
@@ -1,3 +1,5 @@
+github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
+github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
 github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
 github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
main.go
@@ -22,6 +22,7 @@ var (
 	flagTheme         string
 	flagThemeLight    string
 	flagThemeDark     string
+	flagConfig        string
 	flagPreviewThemes bool
 	flagMinify        bool
 	flagGzip          bool
@@ -74,6 +75,7 @@ func main() {
 	flag.StringVar(&flagTheme, "theme", "github", "Style theme")
 	flag.StringVar(&flagThemeLight, "theme-light", "", "Light theme for code highlighting (overrides --theme)")
 	flag.StringVar(&flagThemeDark, "theme-dark", "", "Dark theme for code highlighting (overrides --theme)")
+	flag.StringVar(&flagConfig, "config", "", "Path to TOML config file for multi-repo support")
 	flag.BoolVar(&flagPreviewThemes, "preview-themes", false, "Preview available themes")
 	flag.BoolVar(&flagMinify, "minify", false, "Minify all generated HTML files")
 	flag.BoolVar(&flagGzip, "gzip", false, "Compress all generated HTML files")