source: code/trunk/vendor/git.sr.ht/~emersion/go-scfg/reader.go@ 822

Last change on this file since 822 was 822, checked in by yakumo.izuru, 22 months ago

Prefer immortal.run over runit and rc.d, use vendored modules
for convenience.

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 2.8 KB
RevLine 
[822]1// Package scfg parses configuration files.
2package scfg
3
4import (
5 "bufio"
6 "fmt"
7 "io"
8 "os"
9
10 "github.com/google/shlex"
11)
12
13// Block is a list of directives.
14type Block []*Directive
15
16// GetAll returns a list of directives with the provided name.
17func (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.
28func (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.
38type 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.
47func (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.
61func 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.
72func 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).
87func 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}
Note: See TracBrowser for help on using the repository browser.