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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
// Package scfg parses configuration files.
package scfg
import (
"bufio"
"fmt"
"io"
"os"
"github.com/google/shlex"
)
// Block is a list of directives.
type Block []*Directive
// GetAll returns a list of directives with the provided name.
func (blk Block) GetAll(name string) []*Directive {
l := make([]*Directive, 0, len(blk))
for _, child := range blk {
if child.Name == name {
l = append(l, child)
}
}
return l
}
// Get returns the first directive with the provided name.
func (blk Block) Get(name string) *Directive {
for _, child := range blk {
if child.Name == name {
return child
}
}
return nil
}
// Directive is a configuration directive.
type Directive struct {
Name string
Params []string
Children Block
}
// ParseParams extracts parameters from the directive. It errors out if the
// user hasn't provided enough parameters.
func (d *Directive) ParseParams(params ...*string) error {
if len(d.Params) < len(params) {
return fmt.Errorf("directive %q: want %v params, got %v", d.Name, len(params), len(d.Params))
}
for i, ptr := range params {
if ptr == nil {
continue
}
*ptr = d.Params[i]
}
return nil
}
// Load loads a configuration file.
func Load(path string) (Block, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return Read(f)
}
// Read parses a configuration file from an io.Reader.
func Read(r io.Reader) (Block, error) {
scanner := bufio.NewScanner(r)
block, closingBrace, err := readBlock(scanner)
if err != nil {
return nil, err
} else if closingBrace {
return nil, fmt.Errorf("unexpected '}'")
}
return block, scanner.Err()
}
// readBlock reads a block. closingBrace is true if parsing stopped on '}'
// (otherwise, it stopped on Scanner.Scan).
func readBlock(scanner *bufio.Scanner) (block Block, closingBrace bool, err error) {
for scanner.Scan() {
l := scanner.Text()
words, err := shlex.Split(l)
if err != nil {
return nil, false, fmt.Errorf("failed to parse configuration file: %v", err)
} else if len(words) == 0 {
continue
}
if len(words) == 1 && l[len(l)-1] == '}' {
closingBrace = true
break
}
var d *Directive
if words[len(words)-1] == "{" && l[len(l)-1] == '{' {
words = words[:len(words)-1]
var name string
params := words
if len(words) > 0 {
name, params = words[0], words[1:]
}
childBlock, childClosingBrace, err := readBlock(scanner)
if err != nil {
return nil, false, err
} else if !childClosingBrace {
return nil, false, io.ErrUnexpectedEOF
}
// Allows callers to tell apart "no block" and "empty block"
if childBlock == nil {
childBlock = Block{}
}
d = &Directive{Name: name, Params: params, Children: childBlock}
} else {
d = &Directive{Name: words[0], Params: words[1:]}
}
block = append(block, d)
}
return block, closingBrace, nil
}
|