Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
6.8 KiB
273 lines
6.8 KiB
package gcfg |
|
|
|
import ( |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"os" |
|
"strings" |
|
|
|
"github.com/go-git/gcfg/scanner" |
|
"github.com/go-git/gcfg/token" |
|
"gopkg.in/warnings.v0" |
|
) |
|
|
|
var unescape = map[rune]rune{'\\': '\\', '"': '"', 'n': '\n', 't': '\t', 'b': '\b'} |
|
|
|
// 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 := ioutil.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 := ioutil.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 := ioutil.ReadAll(f) |
|
if err != nil { |
|
return err |
|
} |
|
fset := token.NewFileSet() |
|
file := fset.AddFile(filename, fset.Base(), len(src)) |
|
return readInto(config, fset, file, src) |
|
}
|
|
|