// Package config loads embedded defaults and optionally merges a user // overlay from ~/.config/archduino/overlay.yaml. package config import ( _ "embed" "fmt" "os" "path/filepath" "gopkg.in/yaml.v3" ) //go:embed defaults.yaml var defaultsYAML []byte type Packages struct { Base []string `yaml:"base"` } type ArduinoCLIConfig struct { Version string `yaml:"version"` URL string `yaml:"url"` Cores []string `yaml:"cores"` } type ZshPlugin struct { Name string `yaml:"name"` Repo string `yaml:"repo"` } type ZshConfig struct { OhMyZshRepo string `yaml:"oh_my_zsh_repo"` Plugins []ZshPlugin `yaml:"plugins"` } type ZellijConfig struct { ConfigURL string `yaml:"config_url"` } type ThemesConfig struct { Source string `yaml:"source"` } type ShellConfigBlock struct { Name string `yaml:"name"` File string `yaml:"file"` Content string `yaml:"content"` } type ShellConfigSection struct { Blocks []ShellConfigBlock `yaml:"blocks"` } type NeovimConfig struct { NvChadStarterRepo string `yaml:"nvchad_starter_repo"` } type RenodePlatform struct { Name string `yaml:"name"` ReplPath string `yaml:"repl_path"` Machine string `yaml:"machine"` } type RenodeConfig struct { WokwiVersion string `yaml:"wokwi_version"` WokwiURL string `yaml:"wokwi_url"` Platforms []RenodePlatform `yaml:"platforms"` } type Board struct { Name string `yaml:"name"` FQBN string `yaml:"fqbn"` Template string `yaml:"template"` } type Scaffolding struct { Boards []Board `yaml:"boards"` } // Config is the fully-resolved runtime configuration. type Config struct { Version int `yaml:"version"` ExtraPackages []string `yaml:"extra_packages"` DisabledModules []string `yaml:"disabled_modules"` ThemeOverrides map[string]string `yaml:"theme_overrides"` Packages Packages `yaml:"packages"` Fonts []string `yaml:"fonts"` ArduinoCLI ArduinoCLIConfig `yaml:"arduino_cli"` Zsh ZshConfig `yaml:"zsh"` Zellij ZellijConfig `yaml:"zellij"` Themes ThemesConfig `yaml:"themes"` ShellConfig ShellConfigSection `yaml:"shell_config"` Neovim NeovimConfig `yaml:"neovim"` Renode RenodeConfig `yaml:"renode"` Scaffolding Scaffolding `yaml:"scaffolding"` } const currentVersion = 1 // Load returns defaults merged with the overlay at overlayPath (if present). // If overlayPath is "", the default location (~/.config/archduino/overlay.yaml) // is used. A missing overlay is not an error. func Load(overlayPath string) (Config, error) { var cfg Config if err := yaml.Unmarshal(defaultsYAML, &cfg); err != nil { return Config{}, fmt.Errorf("parse embedded defaults: %w", err) } if overlayPath == "" { home, err := os.UserHomeDir() if err != nil { return cfg, nil } overlayPath = filepath.Join(home, ".config", "archduino", "overlay.yaml") } data, err := os.ReadFile(overlayPath) if err != nil { if os.IsNotExist(err) { return cfg, nil } return Config{}, fmt.Errorf("read overlay %s: %w", overlayPath, err) } var overlay Config if err := yaml.Unmarshal(data, &overlay); err != nil { return Config{}, fmt.Errorf("parse overlay %s: %w", overlayPath, err) } if overlay.Version != 0 && overlay.Version != currentVersion { return Config{}, fmt.Errorf("overlay %s: unsupported version %d (want %d)", overlayPath, overlay.Version, currentVersion) } return merge(cfg, overlay), nil } func merge(base, over Config) Config { out := base if len(over.ExtraPackages) > 0 { out.ExtraPackages = append(append([]string{}, base.ExtraPackages...), over.ExtraPackages...) } if len(over.DisabledModules) > 0 { out.DisabledModules = append(append([]string{}, base.DisabledModules...), over.DisabledModules...) } if len(over.ThemeOverrides) > 0 { merged := make(map[string]string, len(base.ThemeOverrides)+len(over.ThemeOverrides)) for k, v := range base.ThemeOverrides { merged[k] = v } for k, v := range over.ThemeOverrides { merged[k] = v } out.ThemeOverrides = merged } if len(over.Packages.Base) > 0 { out.Packages = over.Packages } if len(over.Fonts) > 0 { out.Fonts = over.Fonts } if over.ArduinoCLI.Version != "" { out.ArduinoCLI = over.ArduinoCLI } if over.Zsh.OhMyZshRepo != "" { out.Zsh = over.Zsh } if over.Zellij.ConfigURL != "" { out.Zellij = over.Zellij } if over.Themes.Source != "" { out.Themes = over.Themes } if len(over.ShellConfig.Blocks) > 0 { out.ShellConfig = over.ShellConfig } if over.Neovim.NvChadStarterRepo != "" { out.Neovim = over.Neovim } if over.Renode.WokwiVersion != "" || len(over.Renode.Platforms) > 0 { out.Renode = over.Renode } if len(over.Scaffolding.Boards) > 0 { out.Scaffolding = over.Scaffolding } return out }