1 // Copyright 2017 Frank Schroeder. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
15 // Encoding specifies encoding of the input data.
19 // UTF8 interprets the input data as UTF-8.
20 UTF8 Encoding = 1 << iota
22 // ISO_8859_1 interprets the input data as ISO-8859-1.
26 // Load reads a buffer into a Properties struct.
27 func Load(buf []byte, enc Encoding) (*Properties, error) {
28 return loadBuf(buf, enc)
31 // LoadString reads an UTF8 string into a properties struct.
32 func LoadString(s string) (*Properties, error) {
33 return loadBuf([]byte(s), UTF8)
36 // LoadMap creates a new Properties struct from a string map.
37 func LoadMap(m map[string]string) *Properties {
45 // LoadFile reads a file into a Properties struct.
46 func LoadFile(filename string, enc Encoding) (*Properties, error) {
47 return loadAll([]string{filename}, enc, false)
50 // LoadFiles reads multiple files in the given order into
51 // a Properties struct. If 'ignoreMissing' is true then
52 // non-existent files will not be reported as error.
53 func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
54 return loadAll(filenames, enc, ignoreMissing)
57 // LoadURL reads the content of the URL into a Properties struct.
59 // The encoding is determined via the Content-Type header which
60 // should be set to 'text/plain'. If the 'charset' parameter is
61 // missing, 'iso-8859-1' or 'latin1' the encoding is set to
62 // ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the
63 // encoding is set to UTF-8. A missing content type header is
64 // interpreted as 'text/plain; charset=utf-8'.
65 func LoadURL(url string) (*Properties, error) {
66 return loadAll([]string{url}, UTF8, false)
69 // LoadURLs reads the content of multiple URLs in the given order into a
70 // Properties struct. If 'ignoreMissing' is true then a 404 status code will
71 // not be reported as error. See LoadURL for the Content-Type header
73 func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) {
74 return loadAll(urls, UTF8, ignoreMissing)
77 // LoadAll reads the content of multiple URLs or files in the given order into a
78 // Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
79 // not be reported as error. Encoding sets the encoding for files. For the URLs please see
80 // LoadURL for the Content-Type header and the encoding.
81 func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
82 return loadAll(names, enc, ignoreMissing)
85 // MustLoadString reads an UTF8 string into a Properties struct and
87 func MustLoadString(s string) *Properties {
88 return must(LoadString(s))
91 // MustLoadFile reads a file into a Properties struct and
93 func MustLoadFile(filename string, enc Encoding) *Properties {
94 return must(LoadFile(filename, enc))
97 // MustLoadFiles reads multiple files in the given order into
98 // a Properties struct and panics on error. If 'ignoreMissing'
99 // is true then non-existent files will not be reported as error.
100 func MustLoadFiles(filenames []string, enc Encoding, ignoreMissing bool) *Properties {
101 return must(LoadFiles(filenames, enc, ignoreMissing))
104 // MustLoadURL reads the content of a URL into a Properties struct and
106 func MustLoadURL(url string) *Properties {
107 return must(LoadURL(url))
110 // MustLoadURLs reads the content of multiple URLs in the given order into a
111 // Properties struct and panics on error. If 'ignoreMissing' is true then a 404
112 // status code will not be reported as error.
113 func MustLoadURLs(urls []string, ignoreMissing bool) *Properties {
114 return must(LoadURLs(urls, ignoreMissing))
117 // MustLoadAll reads the content of multiple URLs or files in the given order into a
118 // Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
119 // not be reported as error. Encoding sets the encoding for files. For the URLs please see
120 // LoadURL for the Content-Type header and the encoding. It panics on error.
121 func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties {
122 return must(LoadAll(names, enc, ignoreMissing))
125 func loadBuf(buf []byte, enc Encoding) (*Properties, error) {
126 p, err := parse(convert(buf, enc))
133 func loadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
134 result := NewProperties()
135 for _, name := range names {
136 n, err := expandName(name)
141 if strings.HasPrefix(n, "http://") || strings.HasPrefix(n, "https://") {
142 p, err = loadURL(n, ignoreMissing)
144 p, err = loadFile(n, enc, ignoreMissing)
152 return result, result.check()
155 func loadFile(filename string, enc Encoding, ignoreMissing bool) (*Properties, error) {
156 data, err := ioutil.ReadFile(filename)
158 if ignoreMissing && os.IsNotExist(err) {
159 LogPrintf("properties: %s not found. skipping", filename)
160 return NewProperties(), nil
164 p, err := parse(convert(data, enc))
171 func loadURL(url string, ignoreMissing bool) (*Properties, error) {
172 resp, err := http.Get(url)
174 return nil, fmt.Errorf("properties: error fetching %q. %s", url, err)
176 if resp.StatusCode == 404 && ignoreMissing {
177 LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode)
178 return NewProperties(), nil
180 if resp.StatusCode != 200 {
181 return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode)
183 body, err := ioutil.ReadAll(resp.Body)
185 return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
187 if err = resp.Body.Close(); err != nil {
188 return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
191 ct := resp.Header.Get("Content-Type")
193 switch strings.ToLower(ct) {
194 case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; charset=latin1":
196 case "", "text/plain; charset=utf-8":
199 return nil, fmt.Errorf("properties: invalid content type %s", ct)
202 p, err := parse(convert(body, enc))
209 func must(p *Properties, err error) *Properties {
216 // expandName expands ${ENV_VAR} expressions in a name.
217 // If the environment variable does not exist then it will be replaced
218 // with an empty string. Malformed expressions like "${ENV_VAR" will
219 // be reported as error.
220 func expandName(name string) (string, error) {
221 return expand(name, make(map[string]bool), "${", "}", make(map[string]string))
224 // Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string.
225 // For ISO-8859-1 we can convert each byte straight into a rune since the
226 // first 256 unicode code points cover ISO-8859-1.
227 func convert(buf []byte, enc Encoding) string {
232 runes := make([]rune, len(buf))
233 for i, b := range buf {
238 ErrorHandler(fmt.Errorf("unsupported encoding %v", enc))
240 panic("ErrorHandler should exit")