forked from toolshed/abra
		
	
		
			
				
	
	
		
			274 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package gcfg
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 
 | |
| 	"gopkg.in/warnings.v0"
 | |
| 
 | |
| 	"github.com/go-git/gcfg/scanner"
 | |
| 	"github.com/go-git/gcfg/token"
 | |
| )
 | |
| 
 | |
| var unescape = map[rune]rune{'\\': '\\', '"': '"', 'n': '\n', 't': '\t', 'b': '\b', '\n': '\n'}
 | |
| 
 | |
| // no error: invalid literals should be caught by scanner
 | |
| func unquote(s string) string {
 | |
| 	u, q, esc := make([]rune, 0, len(s)), false, false
 | |
| 	for _, c := range s {
 | |
| 		if esc {
 | |
| 			uc, ok := unescape[c]
 | |
| 			switch {
 | |
| 			case ok:
 | |
| 				u = append(u, uc)
 | |
| 				fallthrough
 | |
| 			case !q && c == '\n':
 | |
| 				esc = false
 | |
| 				continue
 | |
| 			}
 | |
| 			panic("invalid escape sequence")
 | |
| 		}
 | |
| 		switch c {
 | |
| 		case '"':
 | |
| 			q = !q
 | |
| 		case '\\':
 | |
| 			esc = true
 | |
| 		default:
 | |
| 			u = append(u, c)
 | |
| 		}
 | |
| 	}
 | |
| 	if q {
 | |
| 		panic("missing end quote")
 | |
| 	}
 | |
| 	if esc {
 | |
| 		panic("invalid escape sequence")
 | |
| 	}
 | |
| 	return string(u)
 | |
| }
 | |
| 
 | |
| func read(c *warnings.Collector, callback func(string, string, string, string, bool) error,
 | |
| 	fset *token.FileSet, file *token.File, src []byte) error {
 | |
| 	//
 | |
| 	var s scanner.Scanner
 | |
| 	var errs scanner.ErrorList
 | |
| 	s.Init(file, src, func(p token.Position, m string) { errs.Add(p, m) }, 0)
 | |
| 	sect, sectsub := "", ""
 | |
| 	pos, tok, lit := s.Scan()
 | |
| 	errfn := func(msg string) error {
 | |
| 		return fmt.Errorf("%s: %s", fset.Position(pos), msg)
 | |
| 	}
 | |
| 	for {
 | |
| 		if errs.Len() > 0 {
 | |
| 			if err := c.Collect(errs.Err()); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 		switch tok {
 | |
| 		case token.EOF:
 | |
| 			return nil
 | |
| 		case token.EOL, token.COMMENT:
 | |
| 			pos, tok, lit = s.Scan()
 | |
| 		case token.LBRACK:
 | |
| 			pos, tok, lit = s.Scan()
 | |
| 			if errs.Len() > 0 {
 | |
| 				if err := c.Collect(errs.Err()); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			if tok != token.IDENT {
 | |
| 				if err := c.Collect(errfn("expected section name")); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			sect, sectsub = lit, ""
 | |
| 			pos, tok, lit = s.Scan()
 | |
| 			if errs.Len() > 0 {
 | |
| 				if err := c.Collect(errs.Err()); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			if tok == token.STRING {
 | |
| 				sectsub = unquote(lit)
 | |
| 				if sectsub == "" {
 | |
| 					if err := c.Collect(errfn("empty subsection name")); err != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 				}
 | |
| 				pos, tok, lit = s.Scan()
 | |
| 				if errs.Len() > 0 {
 | |
| 					if err := c.Collect(errs.Err()); err != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			if tok != token.RBRACK {
 | |
| 				if sectsub == "" {
 | |
| 					if err := c.Collect(errfn("expected subsection name or right bracket")); err != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 				}
 | |
| 				if err := c.Collect(errfn("expected right bracket")); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			pos, tok, lit = s.Scan()
 | |
| 			if tok != token.EOL && tok != token.EOF && tok != token.COMMENT {
 | |
| 				if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			// If a section/subsection header was found, ensure a
 | |
| 			// container object is created, even if there are no
 | |
| 			// variables further down.
 | |
| 			err := c.Collect(callback(sect, sectsub, "", "", true))
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		case token.IDENT:
 | |
| 			if sect == "" {
 | |
| 				if err := c.Collect(errfn("expected section header")); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			n := lit
 | |
| 			pos, tok, lit = s.Scan()
 | |
| 			if errs.Len() > 0 {
 | |
| 				return errs.Err()
 | |
| 			}
 | |
| 			blank, v := tok == token.EOF || tok == token.EOL || tok == token.COMMENT, ""
 | |
| 			if !blank {
 | |
| 				if tok != token.ASSIGN {
 | |
| 					if err := c.Collect(errfn("expected '='")); err != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 				}
 | |
| 				pos, tok, lit = s.Scan()
 | |
| 				if errs.Len() > 0 {
 | |
| 					if err := c.Collect(errs.Err()); err != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 				}
 | |
| 				if tok != token.STRING {
 | |
| 					if err := c.Collect(errfn("expected value")); err != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 				}
 | |
| 				v = unquote(lit)
 | |
| 				pos, tok, lit = s.Scan()
 | |
| 				if errs.Len() > 0 {
 | |
| 					if err := c.Collect(errs.Err()); err != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 				}
 | |
| 				if tok != token.EOL && tok != token.EOF && tok != token.COMMENT {
 | |
| 					if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			err := c.Collect(callback(sect, sectsub, n, v, blank))
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		default:
 | |
| 			if sect == "" {
 | |
| 				if err := c.Collect(errfn("expected section header")); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			if err := c.Collect(errfn("expected section header or variable declaration")); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	panic("never reached")
 | |
| }
 | |
| 
 | |
| func readInto(config interface{}, fset *token.FileSet, file *token.File,
 | |
| 	src []byte) error {
 | |
| 	//
 | |
| 	c := warnings.NewCollector(isFatal)
 | |
| 	firstPassCallback := func(s string, ss string, k string, v string, bv bool) error {
 | |
| 		return set(c, config, s, ss, k, v, bv, false)
 | |
| 	}
 | |
| 	err := read(c, firstPassCallback, fset, file, src)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	secondPassCallback := func(s string, ss string, k string, v string, bv bool) error {
 | |
| 		return set(c, config, s, ss, k, v, bv, true)
 | |
| 	}
 | |
| 	err = read(c, secondPassCallback, fset, file, src)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return c.Done()
 | |
| }
 | |
| 
 | |
| // ReadWithCallback reads gcfg formatted data from reader and calls
 | |
| // callback with each section and option found.
 | |
| //
 | |
| // Callback is called with section, subsection, option key, option value
 | |
| // and blank value flag as arguments.
 | |
| //
 | |
| // When a section is found, callback is called with nil subsection, option key
 | |
| // and option value.
 | |
| //
 | |
| // When a subsection is found, callback is called with nil option key and
 | |
| // option value.
 | |
| //
 | |
| // If blank value flag is true, it means that the value was not set for an option
 | |
| // (as opposed to set to empty string).
 | |
| //
 | |
| // If callback returns an error, ReadWithCallback terminates with an error too.
 | |
| func ReadWithCallback(reader io.Reader, callback func(string, string, string, string, bool) error) error {
 | |
| 	src, err := io.ReadAll(reader)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	fset := token.NewFileSet()
 | |
| 	file := fset.AddFile("", fset.Base(), len(src))
 | |
| 	c := warnings.NewCollector(isFatal)
 | |
| 
 | |
| 	return read(c, callback, fset, file, src)
 | |
| }
 | |
| 
 | |
| // ReadInto reads gcfg formatted data from reader and sets the values into the
 | |
| // corresponding fields in config.
 | |
| func ReadInto(config interface{}, reader io.Reader) error {
 | |
| 	src, err := io.ReadAll(reader)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	fset := token.NewFileSet()
 | |
| 	file := fset.AddFile("", fset.Base(), len(src))
 | |
| 	return readInto(config, fset, file, src)
 | |
| }
 | |
| 
 | |
| // ReadStringInto reads gcfg formatted data from str and sets the values into
 | |
| // the corresponding fields in config.
 | |
| func ReadStringInto(config interface{}, str string) error {
 | |
| 	r := strings.NewReader(str)
 | |
| 	return ReadInto(config, r)
 | |
| }
 | |
| 
 | |
| // ReadFileInto reads gcfg formatted data from the file filename and sets the
 | |
| // values into the corresponding fields in config.
 | |
| func ReadFileInto(config interface{}, filename string) error {
 | |
| 	f, err := os.Open(filename)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 	src, err := io.ReadAll(f)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	fset := token.NewFileSet()
 | |
| 	file := fset.AddFile(filename, fset.Base(), len(src))
 | |
| 	return readInto(config, fset, file, src)
 | |
| }
 |