// Copyright 2013-2014 Frank Schroeder. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package properties import ( "fmt" "io/ioutil" "os" ) // Encoding specifies encoding of the input data. type Encoding uint const ( // UTF8 interprets the input data as UTF-8. UTF8 Encoding = 1 << iota // ISO_8859_1 interprets the input data as ISO-8859-1. ISO_8859_1 ) // Load reads a buffer into a Properties struct. func Load(buf []byte, enc Encoding) (*Properties, error) { return loadBuf(buf, enc) } // LoadFile reads a file into a Properties struct. func LoadFile(filename string, enc Encoding) (*Properties, error) { return loadFiles([]string{filename}, enc, false) } // LoadFiles reads multiple files in the given order into // a Properties struct. If 'ignoreMissing' is true then // non-existent files will not be reported as error. func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) { return loadFiles(filenames, enc, ignoreMissing) } // MustLoadFile reads a file into a Properties struct and // panics on error. func MustLoadFile(filename string, enc Encoding) *Properties { return mustLoadFiles([]string{filename}, enc, false) } // MustLoadFiles reads multiple files in the given order into // a Properties struct and panics on error. If 'ignoreMissing' // is true then non-existent files will not be reported as error. func MustLoadFiles(filenames []string, enc Encoding, ignoreMissing bool) *Properties { return mustLoadFiles(filenames, enc, ignoreMissing) } // ---------------------------------------------------------------------------- func loadBuf(buf []byte, enc Encoding) (*Properties, error) { p, err := parse(convert(buf, enc)) if err != nil { return nil, err } return p, p.check() } func loadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) { buff := make([]byte, 0, 4096) for _, filename := range filenames { f, err := expandFilename(filename) if err != nil { return nil, err } buf, err := ioutil.ReadFile(f) if err != nil { if ignoreMissing && os.IsNotExist(err) { // TODO(frank): should we log that we are skipping the file? continue } return nil, err } // concatenate the buffers and add a new line in case // the previous file didn't end with a new line buff = append(append(buff, buf...), '\n') } return loadBuf(buff, enc) } func mustLoadFiles(filenames []string, enc Encoding, ignoreMissing bool) *Properties { p, err := loadFiles(filenames, enc, ignoreMissing) if err != nil { ErrorHandler(err) } return p } // expandFilename expands ${ENV_VAR} expressions in a filename. // If the environment variable does not exist then it will be replaced // with an empty string. Malformed expressions like "${ENV_VAR" will // be reported as error. func expandFilename(filename string) (string, error) { return expand(filename, make(map[string]bool), "${", "}", make(map[string]string)) } // Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string. // For ISO-8859-1 we can convert each byte straight into a rune since the // first 256 unicode code points cover ISO-8859-1. func convert(buf []byte, enc Encoding) string { switch enc { case UTF8: return string(buf) case ISO_8859_1: runes := make([]rune, len(buf)) for i, b := range buf { runes[i] = rune(b) } return string(runes) default: ErrorHandler(fmt.Errorf("unsupported encoding %v", enc)) } panic("ErrorHandler should exit") }