]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/go-ini/ini/ini.go
Merge pull request #27 from terraform-providers/go-modules-2019-02-22
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / go-ini / ini / ini.go
CommitLineData
bae9f6d2
JC
1// Copyright 2014 Unknwon
2//
3// Licensed under the Apache License, Version 2.0 (the "License"): you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15// Package ini provides INI file read and write functionality in Go.
16package ini
17
18import (
19 "bytes"
20 "errors"
21 "fmt"
22 "io"
23 "io/ioutil"
24 "os"
25 "regexp"
26 "runtime"
27 "strconv"
28 "strings"
29 "sync"
30 "time"
31)
32
33const (
34 // Name for default section. You can use this constant or the string literal.
35 // In most of cases, an empty string is all you need to access the section.
36 DEFAULT_SECTION = "DEFAULT"
37
38 // Maximum allowed depth when recursively substituing variable names.
39 _DEPTH_VALUES = 99
15c0b25d 40 _VERSION = "1.25.4"
bae9f6d2
JC
41)
42
43// Version returns current package version literal.
44func Version() string {
45 return _VERSION
46}
47
48var (
49 // Delimiter to determine or compose a new line.
50 // This variable will be changed to "\r\n" automatically on Windows
51 // at package init time.
52 LineBreak = "\n"
53
54 // Variable regexp pattern: %(variable)s
55 varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
56
57 // Indicate whether to align "=" sign with spaces to produce pretty output
58 // or reduce all possible spaces for compact format.
59 PrettyFormat = true
60
61 // Explicitly write DEFAULT section header
62 DefaultHeader = false
63)
64
65func init() {
66 if runtime.GOOS == "windows" {
67 LineBreak = "\r\n"
68 }
69}
70
71func inSlice(str string, s []string) bool {
72 for _, v := range s {
73 if str == v {
74 return true
75 }
76 }
77 return false
78}
79
80// dataSource is an interface that returns object which can be read and closed.
81type dataSource interface {
82 ReadCloser() (io.ReadCloser, error)
83}
84
85// sourceFile represents an object that contains content on the local file system.
86type sourceFile struct {
87 name string
88}
89
90func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
91 return os.Open(s.name)
92}
93
94type bytesReadCloser struct {
95 reader io.Reader
96}
97
98func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
99 return rc.reader.Read(p)
100}
101
102func (rc *bytesReadCloser) Close() error {
103 return nil
104}
105
106// sourceData represents an object that contains content in memory.
107type sourceData struct {
108 data []byte
109}
110
111func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
112 return ioutil.NopCloser(bytes.NewReader(s.data)), nil
113}
114
115// sourceReadCloser represents an input stream with Close method.
116type sourceReadCloser struct {
117 reader io.ReadCloser
118}
119
120func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
121 return s.reader, nil
122}
123
124// File represents a combination of a or more INI file(s) in memory.
125type File struct {
126 // Should make things safe, but sometimes doesn't matter.
127 BlockMode bool
128 // Make sure data is safe in multiple goroutines.
129 lock sync.RWMutex
130
131 // Allow combination of multiple data sources.
132 dataSources []dataSource
133 // Actual data is stored here.
134 sections map[string]*Section
135
136 // To keep data in order.
137 sectionList []string
138
139 options LoadOptions
140
141 NameMapper
142 ValueMapper
143}
144
145// newFile initializes File object with given data sources.
146func newFile(dataSources []dataSource, opts LoadOptions) *File {
147 return &File{
148 BlockMode: true,
149 dataSources: dataSources,
150 sections: make(map[string]*Section),
151 sectionList: make([]string, 0, 10),
152 options: opts,
153 }
154}
155
156func parseDataSource(source interface{}) (dataSource, error) {
157 switch s := source.(type) {
158 case string:
159 return sourceFile{s}, nil
160 case []byte:
161 return &sourceData{s}, nil
162 case io.ReadCloser:
163 return &sourceReadCloser{s}, nil
164 default:
165 return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
166 }
167}
168
169type LoadOptions struct {
170 // Loose indicates whether the parser should ignore nonexistent files or return error.
171 Loose bool
172 // Insensitive indicates whether the parser forces all section and key names to lowercase.
173 Insensitive bool
174 // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
175 IgnoreContinuation bool
176 // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
177 // This type of keys are mostly used in my.cnf.
178 AllowBooleanKeys bool
15c0b25d
AP
179 // AllowShadows indicates whether to keep track of keys with same name under same section.
180 AllowShadows bool
bae9f6d2
JC
181 // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
182 // conform to key/value pairs. Specify the names of those blocks here.
183 UnparseableSections []string
184}
185
186func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
187 sources := make([]dataSource, len(others)+1)
188 sources[0], err = parseDataSource(source)
189 if err != nil {
190 return nil, err
191 }
192 for i := range others {
193 sources[i+1], err = parseDataSource(others[i])
194 if err != nil {
195 return nil, err
196 }
197 }
198 f := newFile(sources, opts)
199 if err = f.Reload(); err != nil {
200 return nil, err
201 }
202 return f, nil
203}
204
205// Load loads and parses from INI data sources.
206// Arguments can be mixed of file name with string type, or raw data in []byte.
207// It will return error if list contains nonexistent files.
208func Load(source interface{}, others ...interface{}) (*File, error) {
209 return LoadSources(LoadOptions{}, source, others...)
210}
211
212// LooseLoad has exactly same functionality as Load function
213// except it ignores nonexistent files instead of returning error.
214func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
215 return LoadSources(LoadOptions{Loose: true}, source, others...)
216}
217
218// InsensitiveLoad has exactly same functionality as Load function
219// except it forces all section and key names to be lowercased.
220func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
221 return LoadSources(LoadOptions{Insensitive: true}, source, others...)
222}
223
15c0b25d
AP
224// InsensitiveLoad has exactly same functionality as Load function
225// except it allows have shadow keys.
226func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
227 return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
228}
229
bae9f6d2
JC
230// Empty returns an empty file object.
231func Empty() *File {
232 // Ignore error here, we sure our data is good.
233 f, _ := Load([]byte(""))
234 return f
235}
236
237// NewSection creates a new section.
238func (f *File) NewSection(name string) (*Section, error) {
239 if len(name) == 0 {
240 return nil, errors.New("error creating new section: empty section name")
241 } else if f.options.Insensitive && name != DEFAULT_SECTION {
242 name = strings.ToLower(name)
243 }
244
245 if f.BlockMode {
246 f.lock.Lock()
247 defer f.lock.Unlock()
248 }
249
250 if inSlice(name, f.sectionList) {
251 return f.sections[name], nil
252 }
253
254 f.sectionList = append(f.sectionList, name)
255 f.sections[name] = newSection(f, name)
256 return f.sections[name], nil
257}
258
259// NewRawSection creates a new section with an unparseable body.
260func (f *File) NewRawSection(name, body string) (*Section, error) {
261 section, err := f.NewSection(name)
262 if err != nil {
263 return nil, err
264 }
265
266 section.isRawSection = true
267 section.rawBody = body
268 return section, nil
269}
270
271// NewSections creates a list of sections.
272func (f *File) NewSections(names ...string) (err error) {
273 for _, name := range names {
274 if _, err = f.NewSection(name); err != nil {
275 return err
276 }
277 }
278 return nil
279}
280
281// GetSection returns section by given name.
282func (f *File) GetSection(name string) (*Section, error) {
283 if len(name) == 0 {
284 name = DEFAULT_SECTION
285 } else if f.options.Insensitive {
286 name = strings.ToLower(name)
287 }
288
289 if f.BlockMode {
290 f.lock.RLock()
291 defer f.lock.RUnlock()
292 }
293
294 sec := f.sections[name]
295 if sec == nil {
296 return nil, fmt.Errorf("section '%s' does not exist", name)
297 }
298 return sec, nil
299}
300
301// Section assumes named section exists and returns a zero-value when not.
302func (f *File) Section(name string) *Section {
303 sec, err := f.GetSection(name)
304 if err != nil {
305 // Note: It's OK here because the only possible error is empty section name,
306 // but if it's empty, this piece of code won't be executed.
307 sec, _ = f.NewSection(name)
308 return sec
309 }
310 return sec
311}
312
313// Section returns list of Section.
314func (f *File) Sections() []*Section {
315 sections := make([]*Section, len(f.sectionList))
316 for i := range f.sectionList {
317 sections[i] = f.Section(f.sectionList[i])
318 }
319 return sections
320}
321
322// SectionStrings returns list of section names.
323func (f *File) SectionStrings() []string {
324 list := make([]string, len(f.sectionList))
325 copy(list, f.sectionList)
326 return list
327}
328
329// DeleteSection deletes a section.
330func (f *File) DeleteSection(name string) {
331 if f.BlockMode {
332 f.lock.Lock()
333 defer f.lock.Unlock()
334 }
335
336 if len(name) == 0 {
337 name = DEFAULT_SECTION
338 }
339
340 for i, s := range f.sectionList {
341 if s == name {
342 f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
343 delete(f.sections, name)
344 return
345 }
346 }
347}
348
349func (f *File) reload(s dataSource) error {
350 r, err := s.ReadCloser()
351 if err != nil {
352 return err
353 }
354 defer r.Close()
355
356 return f.parse(r)
357}
358
359// Reload reloads and parses all data sources.
360func (f *File) Reload() (err error) {
361 for _, s := range f.dataSources {
362 if err = f.reload(s); err != nil {
363 // In loose mode, we create an empty default section for nonexistent files.
364 if os.IsNotExist(err) && f.options.Loose {
365 f.parse(bytes.NewBuffer(nil))
366 continue
367 }
368 return err
369 }
370 }
371 return nil
372}
373
374// Append appends one or more data sources and reloads automatically.
375func (f *File) Append(source interface{}, others ...interface{}) error {
376 ds, err := parseDataSource(source)
377 if err != nil {
378 return err
379 }
380 f.dataSources = append(f.dataSources, ds)
381 for _, s := range others {
382 ds, err = parseDataSource(s)
383 if err != nil {
384 return err
385 }
386 f.dataSources = append(f.dataSources, ds)
387 }
388 return f.Reload()
389}
390
391// WriteToIndent writes content into io.Writer with given indention.
392// If PrettyFormat has been set to be true,
393// it will align "=" sign with spaces under each section.
394func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
395 equalSign := "="
396 if PrettyFormat {
397 equalSign = " = "
398 }
399
400 // Use buffer to make sure target is safe until finish encoding.
401 buf := bytes.NewBuffer(nil)
402 for i, sname := range f.sectionList {
403 sec := f.Section(sname)
404 if len(sec.Comment) > 0 {
405 if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
406 sec.Comment = "; " + sec.Comment
407 }
408 if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
409 return 0, err
410 }
411 }
412
413 if i > 0 || DefaultHeader {
414 if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
415 return 0, err
416 }
417 } else {
418 // Write nothing if default section is empty
419 if len(sec.keyList) == 0 {
420 continue
421 }
422 }
423
424 if sec.isRawSection {
425 if _, err = buf.WriteString(sec.rawBody); err != nil {
426 return 0, err
427 }
428 continue
429 }
430
431 // Count and generate alignment length and buffer spaces using the
432 // longest key. Keys may be modifed if they contain certain characters so
433 // we need to take that into account in our calculation.
434 alignLength := 0
435 if PrettyFormat {
436 for _, kname := range sec.keyList {
437 keyLength := len(kname)
438 // First case will surround key by ` and second by """
439 if strings.ContainsAny(kname, "\"=:") {
440 keyLength += 2
441 } else if strings.Contains(kname, "`") {
442 keyLength += 6
443 }
444
445 if keyLength > alignLength {
446 alignLength = keyLength
447 }
448 }
449 }
450 alignSpaces := bytes.Repeat([]byte(" "), alignLength)
451
15c0b25d 452 KEY_LIST:
bae9f6d2
JC
453 for _, kname := range sec.keyList {
454 key := sec.Key(kname)
455 if len(key.Comment) > 0 {
456 if len(indent) > 0 && sname != DEFAULT_SECTION {
457 buf.WriteString(indent)
458 }
459 if key.Comment[0] != '#' && key.Comment[0] != ';' {
460 key.Comment = "; " + key.Comment
461 }
462 if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
463 return 0, err
464 }
465 }
466
467 if len(indent) > 0 && sname != DEFAULT_SECTION {
468 buf.WriteString(indent)
469 }
470
471 switch {
472 case key.isAutoIncrement:
473 kname = "-"
474 case strings.ContainsAny(kname, "\"=:"):
475 kname = "`" + kname + "`"
476 case strings.Contains(kname, "`"):
477 kname = `"""` + kname + `"""`
478 }
bae9f6d2 479
15c0b25d
AP
480 for _, val := range key.ValueWithShadows() {
481 if _, err = buf.WriteString(kname); err != nil {
482 return 0, err
483 }
bae9f6d2 484
15c0b25d
AP
485 if key.isBooleanType {
486 if kname != sec.keyList[len(sec.keyList)-1] {
487 buf.WriteString(LineBreak)
488 }
489 continue KEY_LIST
490 }
bae9f6d2 491
15c0b25d
AP
492 // Write out alignment spaces before "=" sign
493 if PrettyFormat {
494 buf.Write(alignSpaces[:alignLength-len(kname)])
495 }
496
497 // In case key value contains "\n", "`", "\"", "#" or ";"
498 if strings.ContainsAny(val, "\n`") {
499 val = `"""` + val + `"""`
500 } else if strings.ContainsAny(val, "#;") {
501 val = "`" + val + "`"
502 }
503 if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
504 return 0, err
505 }
bae9f6d2
JC
506 }
507 }
508
509 // Put a line between sections
510 if _, err = buf.WriteString(LineBreak); err != nil {
511 return 0, err
512 }
513 }
514
515 return buf.WriteTo(w)
516}
517
518// WriteTo writes file content into io.Writer.
519func (f *File) WriteTo(w io.Writer) (int64, error) {
520 return f.WriteToIndent(w, "")
521}
522
523// SaveToIndent writes content to file system with given value indention.
524func (f *File) SaveToIndent(filename, indent string) error {
525 // Note: Because we are truncating with os.Create,
526 // so it's safer to save to a temporary file location and rename afte done.
527 tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp"
528 defer os.Remove(tmpPath)
529
530 fw, err := os.Create(tmpPath)
531 if err != nil {
532 return err
533 }
534
535 if _, err = f.WriteToIndent(fw, indent); err != nil {
536 fw.Close()
537 return err
538 }
539 fw.Close()
540
541 // Remove old file and rename the new one.
542 os.Remove(filename)
543 return os.Rename(tmpPath, filename)
544}
545
546// SaveTo writes content to file system.
547func (f *File) SaveTo(filename string) error {
548 return f.SaveToIndent(filename, "")
549}