aboutsummaryrefslogtreecommitdiff
path: root/internal/ini/ini.go
blob: b4fc955335d8598a86454a9b33f46818af6d0acd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Package ini is a tiny dependency-free reader for the boring INI files lectio
// uses for config and calendar data: [sections] (including "a/b" subsections),
// "key = value" pairs (keys may be dotted, e.g. name.pl), and "#"/";" comments.
// It preserves order and never interprets values beyond trimming.
package ini

import (
	"bufio"
	"bytes"
	"fmt"
	"strings"
)

type Pair struct{ Key, Val string }

type Section struct {
	Name  string
	Pairs []Pair
}

// Parse reads INI bytes into ordered sections. Pairs before the first [section]
// go into a leading section with an empty Name.
func Parse(data []byte) ([]Section, error) {
	secs := []Section{{Name: ""}}
	sc := bufio.NewScanner(bytes.NewReader(data))
	sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
	line := 0
	for sc.Scan() {
		line++
		raw := stripComment(sc.Text())
		s := strings.TrimSpace(raw)
		if s == "" {
			continue
		}
		if strings.HasPrefix(s, "[") {
			if !strings.HasSuffix(s, "]") {
				return nil, fmt.Errorf("ini: line %d: unclosed section header %q", line, s)
			}
			name := strings.TrimSpace(s[1 : len(s)-1])
			secs = append(secs, Section{Name: name})
			continue
		}
		eq := strings.IndexByte(s, '=')
		if eq < 0 {
			return nil, fmt.Errorf("ini: line %d: expected key = value, got %q", line, s)
		}
		key := strings.TrimSpace(s[:eq])
		val := strings.TrimSpace(s[eq+1:])
		cur := &secs[len(secs)-1]
		cur.Pairs = append(cur.Pairs, Pair{key, val})
	}
	return secs, sc.Err()
}

// stripComment removes a trailing "#" or ";" comment (our values never contain
// an unescaped # or ;).
func stripComment(line string) string {
	for i := 0; i < len(line); i++ {
		if line[i] == '#' || line[i] == ';' {
			return line[:i]
		}
	}
	return line
}

// List splits a comma-separated value, trimming spaces and dropping empties.
func List(val string) []string {
	var out []string
	for _, p := range strings.Split(val, ",") {
		if t := strings.TrimSpace(p); t != "" {
			out = append(out, t)
		}
	}
	return out
}