Files
gh-action-readme/internal/viper_helper.go
Ismo Vuorinen 7f80105ff5 feat: go 1.25.5, dependency updates, renamed internal/errors (#129)
* feat: rename internal/errors to internal/apperrors

* fix(tests): clear env values before using in tests

* feat: rename internal/errors to internal/apperrors

* chore(deps): update go and all dependencies

* chore: remove renovate from pre-commit, formatting

* chore: sonarcloud fixes

* feat: consolidate constants to appconstants/constants.go

* chore: sonarcloud fixes

* feat: simplification, deduplication, test utils

* chore: sonarcloud fixes

* chore: sonarcloud fixes

* chore: sonarcloud fixes

* chore: sonarcloud fixes

* chore: clean up

* fix: config discovery, const deduplication

* chore: fixes
2026-01-01 23:17:29 +02:00

123 lines
4.4 KiB
Go

package internal
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/adrg/xdg"
"github.com/spf13/viper"
"github.com/ivuorinen/gh-action-readme/appconstants"
)
// initializeViperInstance creates and configures a new viper instance with standard settings.
// This includes XDG-compliant configuration paths, environment variable support,
// and standard search paths for configuration files.
func initializeViperInstance() (*viper.Viper, error) {
v := viper.New()
// Set configuration file name and type
v.SetConfigName(appconstants.ConfigFileName)
v.SetConfigType(appconstants.OutputFormatYAML)
// Add XDG-compliant configuration directory
configDir, err := xdg.ConfigFile(appconstants.PathXDGConfig)
if err != nil {
return nil, fmt.Errorf(appconstants.ErrFailedToGetXDGConfigDir, err)
}
v.AddConfigPath(filepath.Dir(configDir))
// Add additional search paths
v.AddConfigPath(".") // current directory
// Expand home directory for fallback config path
if home, err := os.UserHomeDir(); err == nil {
v.AddConfigPath(filepath.Join(home, ".config", appconstants.AppName)) // fallback
}
v.AddConfigPath(appconstants.PathEtcConfig) // system-wide
// Set environment variable prefix
v.SetEnvPrefix(appconstants.EnvPrefix)
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
v.AutomaticEnv()
return v, nil
}
// setConfigDefaults sets all default configuration values in the viper instance.
// This ensures consistent default values across all configuration loading scenarios.
func setConfigDefaults(v *viper.Viper, defaults *AppConfig) {
v.SetDefault(appconstants.ConfigKeyOrganization, defaults.Organization)
v.SetDefault(appconstants.ConfigKeyRepository, defaults.Repository)
v.SetDefault(appconstants.ConfigKeyVersion, defaults.Version)
v.SetDefault(appconstants.ConfigKeyTheme, defaults.Theme)
v.SetDefault(appconstants.ConfigKeyOutputFormat, defaults.OutputFormat)
v.SetDefault(appconstants.ConfigKeyOutputDir, defaults.OutputDir)
v.SetDefault(appconstants.ConfigKeyTemplate, defaults.Template)
v.SetDefault(appconstants.ConfigKeyHeader, defaults.Header)
v.SetDefault(appconstants.ConfigKeyFooter, defaults.Footer)
v.SetDefault(appconstants.ConfigKeySchema, defaults.Schema)
v.SetDefault(appconstants.ConfigKeyAnalyzeDependencies, defaults.AnalyzeDependencies)
v.SetDefault(appconstants.ConfigKeyShowSecurityInfo, defaults.ShowSecurityInfo)
v.SetDefault(appconstants.ConfigKeyVerbose, defaults.Verbose)
v.SetDefault(appconstants.ConfigKeyQuiet, defaults.Quiet)
v.SetDefault(appconstants.ConfigKeyDefaultsName, defaults.Defaults.Name)
v.SetDefault(appconstants.ConfigKeyDefaultsDescription, defaults.Defaults.Description)
v.SetDefault(appconstants.ConfigKeyDefaultsBrandingIcon, defaults.Defaults.Branding.Icon)
v.SetDefault(appconstants.ConfigKeyDefaultsBrandingColor, defaults.Defaults.Branding.Color)
}
// loadConfigFromViper loads an AppConfig from a specified YAML config file using viper.
func loadConfigFromViper(configPath string) (*AppConfig, error) {
v := viper.New()
v.SetConfigFile(configPath)
v.SetConfigType(appconstants.OutputFormatYAML)
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config %s: %w", configPath, err)
}
var config AppConfig
if err := v.Unmarshal(&config); err != nil {
return nil, fmt.Errorf(appconstants.ErrFailedToUnmarshalConfig, err)
}
return &config, nil
}
// loadAndUnmarshalConfig initializes viper with defaults, reads config file,
// and unmarshals into AppConfig with proper error handling.
// Returns *AppConfig with resolved template paths.
func loadAndUnmarshalConfig(configFile string, v *viper.Viper) (*AppConfig, error) {
// Set defaults
defaults := DefaultAppConfig()
setConfigDefaults(v, defaults)
// Use specific config file if provided
if configFile != "" {
v.SetConfigFile(configFile)
}
// Read configuration
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, fmt.Errorf(appconstants.ErrFailedToReadConfigFile, err)
}
// Config file not found is not an error - we'll use defaults and env vars
}
// Unmarshal configuration into struct
var config AppConfig
if err := v.Unmarshal(&config); err != nil {
return nil, fmt.Errorf(appconstants.ErrFailedToUnmarshalConfig, err)
}
// Resolve template paths relative to binary if they're not absolute
resolveAllTemplatePaths(&config)
return &config, nil
}