/* Copyright © 2026 Craig Williams This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ package config import ( "encoding/json" "fmt" "log/slog" "reflect" "strings" "github.com/spf13/viper" "go.yaml.in/yaml/v3" ) const EnvPrefix string = "JOBSEARCH" const EtcConfigPath string = "/etc/job-search" // Each field's cfg tag must match the section's JSCK* constant. type JobSearchConfig struct { Logging JobSearchConfigLogging `cfg:"logging"` Sources JobSearchConfigSources `cfg:"sources"` } // Default configuration, taken from the default struct tags func LoadDefaultConfig() { var conf JobSearchConfig if err := applyDefaults(&conf); err != nil { panic(fmt.Sprintf("config: %v", err)) } LoadToViper(conf) } func (c JobSearchConfig) Map() map[string]any { return mapOf(c) } func (c JobSearchConfig) Flatten() map[string]any { return flattenOf("", c) } func (c JobSearchConfig) TypeOf(key string) (reflect.Kind, error) { return typeOf(c, key) } func (c JobSearchConfig) ValueOf(key string) (any, error) { return valueOf(c, key) } func (c *JobSearchConfig) SetValue(key string, value any) error { return setValue(c, key, value) } // Utility method for loading a configuration from Viper func LoadFromViper(conf *JobSearchConfig) { keys := viper.AllKeys() for idx := range keys { key := keys[idx] keyType, _ := conf.TypeOf(key) if keyType == reflect.Invalid { continue } switch keyType { case reflect.String: conf.SetValue(key, viper.GetString(key)) case reflect.Int: conf.SetValue(key, viper.GetInt(key)) case reflect.Bool: conf.SetValue(key, viper.GetBool(key)) default: slog.Error(fmt.Sprintf("unknown key type: %s", keyType)) } } } // Utility method for loading a configuration into Viper as defaults func LoadToViper(conf JobSearchConfig) { flatMap := conf.Flatten() for key := range flatMap { viper.SetDefault(key, flatMap[key]) } } // Converts configuration to a string in flat dotted format func (conf JobSearchConfig) String() string { var sb strings.Builder for k, v := range conf.Flatten() { typ, _ := conf.TypeOf(k) sb.WriteString(fmt.Sprint(k, " = ", v, " <", typ, ">\n")) } return sb.String() } // Converts configuration to JSON func (conf JobSearchConfig) JSON() string { confMap := conf.Map() jbytes, _ := json.MarshalIndent(confMap, "", " ") return string(jbytes) } // Converts configuration to YAML func (conf JobSearchConfig) YAML() string { confMap := conf.Map() ybytes, _ := yaml.Marshal(confMap) return string(ybytes) }