main
Raw Download raw file
  1package gcfg
  2
  3import (
  4	"fmt"
  5	"io"
  6	"os"
  7	"strings"
  8
  9	"gopkg.in/warnings.v0"
 10
 11	"github.com/go-git/gcfg/scanner"
 12	"github.com/go-git/gcfg/token"
 13)
 14
 15var unescape = map[rune]rune{'\\': '\\', '"': '"', 'n': '\n', 't': '\t', 'b': '\b', '\n': '\n'}
 16
 17// no error: invalid literals should be caught by scanner
 18func unquote(s string) string {
 19	u, q, esc := make([]rune, 0, len(s)), false, false
 20	for _, c := range s {
 21		if esc {
 22			uc, ok := unescape[c]
 23			switch {
 24			case ok:
 25				u = append(u, uc)
 26				fallthrough
 27			case !q && c == '\n':
 28				esc = false
 29				continue
 30			}
 31			panic("invalid escape sequence")
 32		}
 33		switch c {
 34		case '"':
 35			q = !q
 36		case '\\':
 37			esc = true
 38		default:
 39			u = append(u, c)
 40		}
 41	}
 42	if q {
 43		panic("missing end quote")
 44	}
 45	if esc {
 46		panic("invalid escape sequence")
 47	}
 48	return string(u)
 49}
 50
 51func read(c *warnings.Collector, callback func(string, string, string, string, bool) error,
 52	fset *token.FileSet, file *token.File, src []byte) error {
 53	//
 54	var s scanner.Scanner
 55	var errs scanner.ErrorList
 56	s.Init(file, src, func(p token.Position, m string) { errs.Add(p, m) }, 0)
 57	sect, sectsub := "", ""
 58	pos, tok, lit := s.Scan()
 59	errfn := func(msg string) error {
 60		return fmt.Errorf("%s: %s", fset.Position(pos), msg)
 61	}
 62	for {
 63		if errs.Len() > 0 {
 64			if err := c.Collect(errs.Err()); err != nil {
 65				return err
 66			}
 67		}
 68		switch tok {
 69		case token.EOF:
 70			return nil
 71		case token.EOL, token.COMMENT:
 72			pos, tok, lit = s.Scan()
 73		case token.LBRACK:
 74			pos, tok, lit = s.Scan()
 75			if errs.Len() > 0 {
 76				if err := c.Collect(errs.Err()); err != nil {
 77					return err
 78				}
 79			}
 80			if tok != token.IDENT {
 81				if err := c.Collect(errfn("expected section name")); err != nil {
 82					return err
 83				}
 84			}
 85			sect, sectsub = lit, ""
 86			pos, tok, lit = s.Scan()
 87			if errs.Len() > 0 {
 88				if err := c.Collect(errs.Err()); err != nil {
 89					return err
 90				}
 91			}
 92			if tok == token.STRING {
 93				sectsub = unquote(lit)
 94				if sectsub == "" {
 95					if err := c.Collect(errfn("empty subsection name")); err != nil {
 96						return err
 97					}
 98				}
 99				pos, tok, lit = s.Scan()
100				if errs.Len() > 0 {
101					if err := c.Collect(errs.Err()); err != nil {
102						return err
103					}
104				}
105			}
106			if tok != token.RBRACK {
107				if sectsub == "" {
108					if err := c.Collect(errfn("expected subsection name or right bracket")); err != nil {
109						return err
110					}
111				}
112				if err := c.Collect(errfn("expected right bracket")); err != nil {
113					return err
114				}
115			}
116			pos, tok, lit = s.Scan()
117			if tok != token.EOL && tok != token.EOF && tok != token.COMMENT {
118				if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil {
119					return err
120				}
121			}
122			// If a section/subsection header was found, ensure a
123			// container object is created, even if there are no
124			// variables further down.
125			err := c.Collect(callback(sect, sectsub, "", "", true))
126			if err != nil {
127				return err
128			}
129		case token.IDENT:
130			if sect == "" {
131				if err := c.Collect(errfn("expected section header")); err != nil {
132					return err
133				}
134			}
135			n := lit
136			pos, tok, lit = s.Scan()
137			if errs.Len() > 0 {
138				return errs.Err()
139			}
140			blank, v := tok == token.EOF || tok == token.EOL || tok == token.COMMENT, ""
141			if !blank {
142				if tok != token.ASSIGN {
143					if err := c.Collect(errfn("expected '='")); err != nil {
144						return err
145					}
146				}
147				pos, tok, lit = s.Scan()
148				if errs.Len() > 0 {
149					if err := c.Collect(errs.Err()); err != nil {
150						return err
151					}
152				}
153				if tok != token.STRING {
154					if err := c.Collect(errfn("expected value")); err != nil {
155						return err
156					}
157				}
158				v = unquote(lit)
159				pos, tok, lit = s.Scan()
160				if errs.Len() > 0 {
161					if err := c.Collect(errs.Err()); err != nil {
162						return err
163					}
164				}
165				if tok != token.EOL && tok != token.EOF && tok != token.COMMENT {
166					if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil {
167						return err
168					}
169				}
170			}
171			err := c.Collect(callback(sect, sectsub, n, v, blank))
172			if err != nil {
173				return err
174			}
175		default:
176			if sect == "" {
177				if err := c.Collect(errfn("expected section header")); err != nil {
178					return err
179				}
180			}
181			if err := c.Collect(errfn("expected section header or variable declaration")); err != nil {
182				return err
183			}
184		}
185	}
186	panic("never reached")
187}
188
189func readInto(config interface{}, fset *token.FileSet, file *token.File,
190	src []byte) error {
191	//
192	c := warnings.NewCollector(isFatal)
193	firstPassCallback := func(s string, ss string, k string, v string, bv bool) error {
194		return set(c, config, s, ss, k, v, bv, false)
195	}
196	err := read(c, firstPassCallback, fset, file, src)
197	if err != nil {
198		return err
199	}
200	secondPassCallback := func(s string, ss string, k string, v string, bv bool) error {
201		return set(c, config, s, ss, k, v, bv, true)
202	}
203	err = read(c, secondPassCallback, fset, file, src)
204	if err != nil {
205		return err
206	}
207	return c.Done()
208}
209
210// ReadWithCallback reads gcfg formatted data from reader and calls
211// callback with each section and option found.
212//
213// Callback is called with section, subsection, option key, option value
214// and blank value flag as arguments.
215//
216// When a section is found, callback is called with nil subsection, option key
217// and option value.
218//
219// When a subsection is found, callback is called with nil option key and
220// option value.
221//
222// If blank value flag is true, it means that the value was not set for an option
223// (as opposed to set to empty string).
224//
225// If callback returns an error, ReadWithCallback terminates with an error too.
226func ReadWithCallback(reader io.Reader, callback func(string, string, string, string, bool) error) error {
227	src, err := io.ReadAll(reader)
228	if err != nil {
229		return err
230	}
231
232	fset := token.NewFileSet()
233	file := fset.AddFile("", fset.Base(), len(src))
234	c := warnings.NewCollector(isFatal)
235
236	return read(c, callback, fset, file, src)
237}
238
239// ReadInto reads gcfg formatted data from reader and sets the values into the
240// corresponding fields in config.
241func ReadInto(config interface{}, reader io.Reader) error {
242	src, err := io.ReadAll(reader)
243	if err != nil {
244		return err
245	}
246	fset := token.NewFileSet()
247	file := fset.AddFile("", fset.Base(), len(src))
248	return readInto(config, fset, file, src)
249}
250
251// ReadStringInto reads gcfg formatted data from str and sets the values into
252// the corresponding fields in config.
253func ReadStringInto(config interface{}, str string) error {
254	r := strings.NewReader(str)
255	return ReadInto(config, r)
256}
257
258// ReadFileInto reads gcfg formatted data from the file filename and sets the
259// values into the corresponding fields in config.
260func ReadFileInto(config interface{}, filename string) error {
261	f, err := os.Open(filename)
262	if err != nil {
263		return err
264	}
265	defer f.Close()
266	src, err := io.ReadAll(f)
267	if err != nil {
268		return err
269	}
270	fset := token.NewFileSet()
271	file := fset.AddFile(filename, fset.Base(), len(src))
272	return readInto(config, fset, file, src)
273}