// Package config defines utils to simplify configuration // management package conf import ( "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" ) // LoadFile reads the file at path, parses its json content and fills the struct // with the content of the file. func LoadFile(path string, data interface{}) error { return read(path, data) } // SaveFile writes the given data serialized in JSON in the given path func SaveFile(path string, data interface{}) error { return write(path, data) } // LoadAndUpdateFile reads the config fileat path and // updates it, meaning that it adds new options, removes // old ones, and update it by calling the Update method of // data if it implements the interface Updater. // // If no file is found at path, it is created and // initialized with the default values. // // An error is returned only if the config file cannot be // written. func LoadAndUpdateFile(path string, data interface{}) error { if _, err := os.Stat(path); !os.IsNotExist(err) { err2 := read(path, data) if err2 != nil { return err2 } if data, ok := data.(Updater); ok { data.Update() } } return write(path, data) } // Updater is the interface that can be implemented by // config structs. If it is implemented, Update() is // called by LoadAndUpdateFile(). It allows one to modify // the data and persist those changes, for example to // change default values type Updater interface { Update() } func read(path string, data interface{}) error { content, err := ioutil.ReadFile(filepath.Clean(path)) if err != nil { return fmt.Errorf("cannot read config file: %w", err) } err = json.Unmarshal(content, &data) if err != nil { return fmt.Errorf("cannot parse config file: %w", err) } return nil } func write(path string, data interface{}) error { content, err := json.MarshalIndent(data, "", " ") if err != nil { return fmt.Errorf("cannot generate config content: %w", err) } err = ioutil.WriteFile(path, content, 0644) if err != nil { return fmt.Errorf("cannot write config file '%s': %w", path, err) } return nil }