package ini // Scan a databuffer of INI-formatted data. import ( "fmt" ) type scanState int type scanFunction func(c byte, e *element) scanState const ( scanContinue scanState = 1 << iota scanEOL scanEOF scanError scanBegin scanSection scanVariable scanVariableValue ) var ( EOF = fmt.Errorf("END OF FILE") EOL = fmt.Errorf("END OF LINE") ) // SyntaxError is returned when the formatting is wrong. type SyntaxError struct { got byte exp byte msg string off int } // Error returns the error as a string func (s SyntaxError) Error() string { if s.got == 0 { return fmt.Sprintf("Error on position '%d: %s'", s.off, s.msg) } if s.exp == 0 { return fmt.Sprintf("Error on position '%d': %s. Got '%c'.", s.off, s.msg, s.got) } return fmt.Sprintf("Error on position '%d': %s. Got '%c' while expecting '%c'.", s.off, s.msg, s.got, s.exp) } type scanner struct { data []byte prev byte scan scanFunction read int len int err error } func newScanner(d []byte) *scanner { s := &scanner{ data: d, prev: 0, read: 0, len: len(d), } s.scan = s.begin return s } func (s *scanner) reset() { s.prev = 0 s.scan = s.begin s.read = 0 s.err = nil } // loop assists next-method func (s *scanner) loop() (*element, error) { e := newElement() for _, c := range s.data[s.read:] { s.read++ state := s.scan(c, e) s.prev = c if s.err != nil { return nil, s.err } switch state { case scanEOL: return e, EOL case scanEOF: return e, EOF } } return e, nil } // Just quickly loops through the INI and returns an error on error func (s *scanner) valid() error { defer s.reset() for _, err := s.next(); ; _, err = s.next() { switch err { case EOL: continue case EOF: return nil default: if err != nil { return err } } } return nil } // next returns one element and most likely an error (typically EOF/EOL) on every iteration func (s *scanner) next() (*element, error) { e, err := s.loop() if e != nil { e.trim() } return e, err } // ret ensures that we return the proper error func (s *scanner) ret(state scanState) scanState { if state != scanEOF && s.read == s.len { state = scanEOF } if state == scanEOL || state == scanEOF { s.scan = s.begin } return state } func (s *scanner) space(c byte) bool { return c == ' ' || c == '\n' || c == '\t' || c == '\r' } func (s *scanner) begin(c byte, e *element) scanState { switch { case s.space(c): return s.ret(scanContinue) case c == '[': e.setType(section) s.scan = s.section return s.ret(scanContinue) default: e.setType(variable) s.scan = s.variable return s.scan(c, e) } } func (s *scanner) section(c byte, e *element) scanState { switch { case s.space(c): s.err = SyntaxError{ got: c, exp: ']', off: s.read, msg: "Invalid space character.", } return scanError case s.read == s.len: s.err = SyntaxError{ got: 0x3, exp: ']', off: s.read, msg: "Invalid EOF", } return scanError case c == ']': return s.ret(scanEOL) default: e.key.add(c) return s.ret(scanContinue) } } func (s *scanner) variable(c byte, e *element) scanState { switch c { case '\n', '\r': return s.ret(scanEOL) case '=': s.scan = s.variableValue return s.ret(scanContinue) default: e.key.add(c) return s.ret(scanContinue) } } func (s *scanner) variableValue(c byte, e *element) scanState { switch c { case '\n', '\r': if e.val.lastByteMatching('\\') >= 0 { e.val.removeLastByteMatching('\\') e.val.add('\n') return s.ret(scanContinue) } return s.ret(scanEOL) default: e.val.add(c) return s.ret(scanContinue) } }