forked from lug/matterbridge
		
	
		
			
				
	
	
		
			242 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2017 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"
 | 
						|
	"net/http"
 | 
						|
	"os"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// 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)
 | 
						|
}
 | 
						|
 | 
						|
// LoadString reads an UTF8 string into a properties struct.
 | 
						|
func LoadString(s string) (*Properties, error) {
 | 
						|
	return loadBuf([]byte(s), UTF8)
 | 
						|
}
 | 
						|
 | 
						|
// LoadMap creates a new Properties struct from a string map.
 | 
						|
func LoadMap(m map[string]string) *Properties {
 | 
						|
	p := NewProperties()
 | 
						|
	for k, v := range m {
 | 
						|
		p.Set(k, v)
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// LoadFile reads a file into a Properties struct.
 | 
						|
func LoadFile(filename string, enc Encoding) (*Properties, error) {
 | 
						|
	return loadAll([]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 loadAll(filenames, enc, ignoreMissing)
 | 
						|
}
 | 
						|
 | 
						|
// LoadURL reads the content of the URL into a Properties struct.
 | 
						|
//
 | 
						|
// The encoding is determined via the Content-Type header which
 | 
						|
// should be set to 'text/plain'. If the 'charset' parameter is
 | 
						|
// missing, 'iso-8859-1' or 'latin1' the encoding is set to
 | 
						|
// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the
 | 
						|
// encoding is set to UTF-8. A missing content type header is
 | 
						|
// interpreted as 'text/plain; charset=utf-8'.
 | 
						|
func LoadURL(url string) (*Properties, error) {
 | 
						|
	return loadAll([]string{url}, UTF8, false)
 | 
						|
}
 | 
						|
 | 
						|
// LoadURLs reads the content of multiple URLs in the given order into a
 | 
						|
// Properties struct. If 'ignoreMissing' is true then a 404 status code will
 | 
						|
// not be reported as error. See LoadURL for the Content-Type header
 | 
						|
// and the encoding.
 | 
						|
func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) {
 | 
						|
	return loadAll(urls, UTF8, ignoreMissing)
 | 
						|
}
 | 
						|
 | 
						|
// LoadAll reads the content of multiple URLs or files in the given order into a
 | 
						|
// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
 | 
						|
// not be reported as error. Encoding sets the encoding for files. For the URLs please see
 | 
						|
// LoadURL for the Content-Type header and the encoding.
 | 
						|
func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
 | 
						|
	return loadAll(names, enc, ignoreMissing)
 | 
						|
}
 | 
						|
 | 
						|
// MustLoadString reads an UTF8 string into a Properties struct and
 | 
						|
// panics on error.
 | 
						|
func MustLoadString(s string) *Properties {
 | 
						|
	return must(LoadString(s))
 | 
						|
}
 | 
						|
 | 
						|
// MustLoadFile reads a file into a Properties struct and
 | 
						|
// panics on error.
 | 
						|
func MustLoadFile(filename string, enc Encoding) *Properties {
 | 
						|
	return must(LoadFile(filename, enc))
 | 
						|
}
 | 
						|
 | 
						|
// 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 must(LoadFiles(filenames, enc, ignoreMissing))
 | 
						|
}
 | 
						|
 | 
						|
// MustLoadURL reads the content of a URL into a Properties struct and
 | 
						|
// panics on error.
 | 
						|
func MustLoadURL(url string) *Properties {
 | 
						|
	return must(LoadURL(url))
 | 
						|
}
 | 
						|
 | 
						|
// MustLoadURLs reads the content of multiple URLs in the given order into a
 | 
						|
// Properties struct and panics on error. If 'ignoreMissing' is true then a 404
 | 
						|
// status code will not be reported as error.
 | 
						|
func MustLoadURLs(urls []string, ignoreMissing bool) *Properties {
 | 
						|
	return must(LoadURLs(urls, ignoreMissing))
 | 
						|
}
 | 
						|
 | 
						|
// MustLoadAll reads the content of multiple URLs or files in the given order into a
 | 
						|
// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
 | 
						|
// not be reported as error. Encoding sets the encoding for files. For the URLs please see
 | 
						|
// LoadURL for the Content-Type header and the encoding. It panics on error.
 | 
						|
func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties {
 | 
						|
	return must(LoadAll(names, 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 loadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
 | 
						|
	result := NewProperties()
 | 
						|
	for _, name := range names {
 | 
						|
		n, err := expandName(name)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		var p *Properties
 | 
						|
		if strings.HasPrefix(n, "http://") || strings.HasPrefix(n, "https://") {
 | 
						|
			p, err = loadURL(n, ignoreMissing)
 | 
						|
		} else {
 | 
						|
			p, err = loadFile(n, enc, ignoreMissing)
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		result.Merge(p)
 | 
						|
 | 
						|
	}
 | 
						|
	return result, result.check()
 | 
						|
}
 | 
						|
 | 
						|
func loadFile(filename string, enc Encoding, ignoreMissing bool) (*Properties, error) {
 | 
						|
	data, err := ioutil.ReadFile(filename)
 | 
						|
	if err != nil {
 | 
						|
		if ignoreMissing && os.IsNotExist(err) {
 | 
						|
			LogPrintf("properties: %s not found. skipping", filename)
 | 
						|
			return NewProperties(), nil
 | 
						|
		}
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	p, err := parse(convert(data, enc))
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return p, nil
 | 
						|
}
 | 
						|
 | 
						|
func loadURL(url string, ignoreMissing bool) (*Properties, error) {
 | 
						|
	resp, err := http.Get(url)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("properties: error fetching %q. %s", url, err)
 | 
						|
	}
 | 
						|
	if resp.StatusCode == 404 && ignoreMissing {
 | 
						|
		LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode)
 | 
						|
		return NewProperties(), nil
 | 
						|
	}
 | 
						|
	if resp.StatusCode != 200 {
 | 
						|
		return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode)
 | 
						|
	}
 | 
						|
	body, err := ioutil.ReadAll(resp.Body)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
 | 
						|
	}
 | 
						|
	if err = resp.Body.Close(); err != nil {
 | 
						|
		return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
 | 
						|
	}
 | 
						|
 | 
						|
	ct := resp.Header.Get("Content-Type")
 | 
						|
	var enc Encoding
 | 
						|
	switch strings.ToLower(ct) {
 | 
						|
	case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; charset=latin1":
 | 
						|
		enc = ISO_8859_1
 | 
						|
	case "", "text/plain; charset=utf-8":
 | 
						|
		enc = UTF8
 | 
						|
	default:
 | 
						|
		return nil, fmt.Errorf("properties: invalid content type %s", ct)
 | 
						|
	}
 | 
						|
 | 
						|
	p, err := parse(convert(body, enc))
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return p, nil
 | 
						|
}
 | 
						|
 | 
						|
func must(p *Properties, err error) *Properties {
 | 
						|
	if err != nil {
 | 
						|
		ErrorHandler(err)
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// expandName expands ${ENV_VAR} expressions in a name.
 | 
						|
// 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 expandName(name string) (string, error) {
 | 
						|
	return expand(name, []string{}, "${", "}", 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")
 | 
						|
}
 |