1 | // Package scfg parses configuration files.
|
---|
2 | package scfg
|
---|
3 |
|
---|
4 | import (
|
---|
5 | "bufio"
|
---|
6 | "fmt"
|
---|
7 | "io"
|
---|
8 | "os"
|
---|
9 |
|
---|
10 | "github.com/google/shlex"
|
---|
11 | )
|
---|
12 |
|
---|
13 | // Block is a list of directives.
|
---|
14 | type Block []*Directive
|
---|
15 |
|
---|
16 | // GetAll returns a list of directives with the provided name.
|
---|
17 | func (blk Block) GetAll(name string) []*Directive {
|
---|
18 | l := make([]*Directive, 0, len(blk))
|
---|
19 | for _, child := range blk {
|
---|
20 | if child.Name == name {
|
---|
21 | l = append(l, child)
|
---|
22 | }
|
---|
23 | }
|
---|
24 | return l
|
---|
25 | }
|
---|
26 |
|
---|
27 | // Get returns the first directive with the provided name.
|
---|
28 | func (blk Block) Get(name string) *Directive {
|
---|
29 | for _, child := range blk {
|
---|
30 | if child.Name == name {
|
---|
31 | return child
|
---|
32 | }
|
---|
33 | }
|
---|
34 | return nil
|
---|
35 | }
|
---|
36 |
|
---|
37 | // Directive is a configuration directive.
|
---|
38 | type Directive struct {
|
---|
39 | Name string
|
---|
40 | Params []string
|
---|
41 |
|
---|
42 | Children Block
|
---|
43 | }
|
---|
44 |
|
---|
45 | // ParseParams extracts parameters from the directive. It errors out if the
|
---|
46 | // user hasn't provided enough parameters.
|
---|
47 | func (d *Directive) ParseParams(params ...*string) error {
|
---|
48 | if len(d.Params) < len(params) {
|
---|
49 | return fmt.Errorf("directive %q: want %v params, got %v", d.Name, len(params), len(d.Params))
|
---|
50 | }
|
---|
51 | for i, ptr := range params {
|
---|
52 | if ptr == nil {
|
---|
53 | continue
|
---|
54 | }
|
---|
55 | *ptr = d.Params[i]
|
---|
56 | }
|
---|
57 | return nil
|
---|
58 | }
|
---|
59 |
|
---|
60 | // Load loads a configuration file.
|
---|
61 | func Load(path string) (Block, error) {
|
---|
62 | f, err := os.Open(path)
|
---|
63 | if err != nil {
|
---|
64 | return nil, err
|
---|
65 | }
|
---|
66 | defer f.Close()
|
---|
67 |
|
---|
68 | return Read(f)
|
---|
69 | }
|
---|
70 |
|
---|
71 | // Read parses a configuration file from an io.Reader.
|
---|
72 | func Read(r io.Reader) (Block, error) {
|
---|
73 | scanner := bufio.NewScanner(r)
|
---|
74 |
|
---|
75 | block, closingBrace, err := readBlock(scanner)
|
---|
76 | if err != nil {
|
---|
77 | return nil, err
|
---|
78 | } else if closingBrace {
|
---|
79 | return nil, fmt.Errorf("unexpected '}'")
|
---|
80 | }
|
---|
81 |
|
---|
82 | return block, scanner.Err()
|
---|
83 | }
|
---|
84 |
|
---|
85 | // readBlock reads a block. closingBrace is true if parsing stopped on '}'
|
---|
86 | // (otherwise, it stopped on Scanner.Scan).
|
---|
87 | func readBlock(scanner *bufio.Scanner) (block Block, closingBrace bool, err error) {
|
---|
88 | for scanner.Scan() {
|
---|
89 | l := scanner.Text()
|
---|
90 | words, err := shlex.Split(l)
|
---|
91 | if err != nil {
|
---|
92 | return nil, false, fmt.Errorf("failed to parse configuration file: %v", err)
|
---|
93 | } else if len(words) == 0 {
|
---|
94 | continue
|
---|
95 | }
|
---|
96 |
|
---|
97 | if len(words) == 1 && l[len(l)-1] == '}' {
|
---|
98 | closingBrace = true
|
---|
99 | break
|
---|
100 | }
|
---|
101 |
|
---|
102 | var d *Directive
|
---|
103 | if words[len(words)-1] == "{" && l[len(l)-1] == '{' {
|
---|
104 | words = words[:len(words)-1]
|
---|
105 |
|
---|
106 | var name string
|
---|
107 | params := words
|
---|
108 | if len(words) > 0 {
|
---|
109 | name, params = words[0], words[1:]
|
---|
110 | }
|
---|
111 |
|
---|
112 | childBlock, childClosingBrace, err := readBlock(scanner)
|
---|
113 | if err != nil {
|
---|
114 | return nil, false, err
|
---|
115 | } else if !childClosingBrace {
|
---|
116 | return nil, false, io.ErrUnexpectedEOF
|
---|
117 | }
|
---|
118 |
|
---|
119 | // Allows callers to tell apart "no block" and "empty block"
|
---|
120 | if childBlock == nil {
|
---|
121 | childBlock = Block{}
|
---|
122 | }
|
---|
123 |
|
---|
124 | d = &Directive{Name: name, Params: params, Children: childBlock}
|
---|
125 | } else {
|
---|
126 | d = &Directive{Name: words[0], Params: words[1:]}
|
---|
127 | }
|
---|
128 | block = append(block, d)
|
---|
129 | }
|
---|
130 |
|
---|
131 | return block, closingBrace, nil
|
---|
132 | }
|
---|