// 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 }