1
0
Fork 0
mirror of https://github.com/Luzifer/password.git synced 2024-12-21 05:11:18 +00:00
password/vendor/github.com/tredoe/osutil/config/shconf/scan.go

357 lines
7.6 KiB
Go
Raw Normal View History

// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package shconf
import (
"bufio"
"bytes"
"io"
"strconv"
"unicode"
"unicode/utf8"
)
type extraCharError int
func (e extraCharError) Error() string {
return strconv.Itoa(int(e)) + ": extra character/s after of the value"
}
type keyError int
func (e keyError) Error() string {
return strconv.Itoa(int(e)) + ": key not found"
}
type noASCIIKeyError int
func (e noASCIIKeyError) Error() string {
return strconv.Itoa(int(e)) + ": the key only must to have ASCII characters"
}
type openQuoteError int
func (e openQuoteError) Error() string {
return strconv.Itoa(int(e)) + ": the quote in the value is not closed"
}
type valueError int
func (e valueError) Error() string {
return strconv.Itoa(int(e)) + ": value not found"
}
// * * *
// _DEF_SEPARATOR is the character used like separator, by default.
var _DEF_SEPARATOR = []byte{'='}
// option represents the option to run the scanner.
type Option uint8
const (
SKIP_KEYS_DISABLED Option = iota + 1
GET_KEYS_DISABLED
)
// Scanner provides a convenient interface for reading data such as a file of
// lines of text in format key-value. Successive calls to the Scan method will
// step through the 'tokens' of a file, skipping the bytes between the tokens.
//
// Scanning stops unrecoverably at EOF, the first I/O error, or a token too
// large to fit in the buffer.
type Scanner struct {
buf *bufio.Reader
// Character/s used to separate the value from key.
// It is only get in the first call to "Scan()".
separator []byte
key []byte
value []byte
err error
line int // Number of line being scanned
}
// NewScanner returns a new Scanner to read from r, with the option to skip the
// keys disabled.
func NewScanner(r io.Reader) *Scanner { return &Scanner{buf: bufio.NewReader(r)} }
// Scan advances the Scanner to the next tokens, which will then be available
// through the Bytes or Text method. It returns false when the scan stops, either
// by reaching the end of the input or an error. After Scan returns false, the Err
// method will return any error that occurred during scanning, except that if it
// was io.EOF, Err will return nil.
func (s *Scanner) Scan() bool {
var thisRune rune
var n int
var err error
for s.line++; ; s.line++ {
// Skip leading spaces.
for thisRune, n, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
if err != nil {
s.err = err
return false
}
if thisRune == '\n' {
s.line++
continue
}
if !unicode.IsSpace(thisRune) {
break
}
}
// Skip line comment and section.
if thisRune == '#' || thisRune == '[' {
if _, err = s.buf.ReadBytes('\n'); err != nil {
s.err = err
return false
}
continue
}
break
}
if thisRune == '=' {
s.err = keyError(s.line)
return false
}
var key, value, separator []rune
// Get key
for ; ; thisRune, n, err = s.buf.ReadRune() {
if err != nil {
if err != io.EOF {
s.err = err
} else {
s.err = valueError(s.line)
}
return false
}
// The separator charater could the the equal sign or space.
if thisRune == '=' || unicode.IsSpace(thisRune) {
break
}
if n > 1 || !isValidChar(thisRune) {
s.err = noASCIIKeyError(s.line)
return false
}
key = append(key, thisRune)
}
// Skip spaces before and after of the separator character.
if unicode.IsSpace(thisRune) {
separator = append(separator, thisRune)
for thisRune, _, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
if err != nil {
if err != io.EOF {
s.err = err
} else {
s.err = valueError(s.line)
}
return false
}
if !unicode.IsSpace(thisRune) {
break
}
separator = append(separator, thisRune)
}
}
if thisRune == '=' {
separator = append(separator, thisRune)
if thisRune, _, err = s.buf.ReadRune(); err != nil {
if err != io.EOF {
s.err = err
} else {
s.err = valueError(s.line)
}
return false
}
}
if unicode.IsSpace(thisRune) {
separator = append(separator, thisRune)
for thisRune, _, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
if err != nil {
if err != io.EOF {
s.err = err
} else {
s.err = valueError(s.line)
}
return false
}
if !unicode.IsSpace(thisRune) {
break
}
separator = append(separator, thisRune)
}
}
// Get value
var valueIsString, valueInDQuote bool
var lastRune rune
if thisRune == '"' { // The value is between double quotes
valueIsString = true
valueInDQuote = true
} else if thisRune == '\'' { // between single quotes
valueIsString = true
} else {
value = append(value, thisRune)
}
for thisRune, _, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
if err != nil {
if err != io.EOF {
s.err = err
return false
}
if valueIsString {
s.err = openQuoteError(s.line)
return false
}
s.err = err
break
}
if valueIsString {
if valueInDQuote {
if thisRune == '"' && lastRune != '\\' {
break
}
} else if thisRune == '\'' && lastRune != '\\' {
break
}
lastRune = thisRune // To checking if it is a quote escaped.
value = append(value, thisRune)
} else {
if unicode.IsSpace(thisRune) {
break
}
value = append(value, thisRune)
}
}
// Sanity check
if thisRune != '\n' && thisRune != '\r' {
doCheck := true
last, _, err := s.buf.ReadLine()
if err != nil {
if err != io.EOF {
s.err = err
return false
} else {
doCheck = false
}
}
if doCheck {
for _, char := range last {
if unicode.IsSpace(rune(char)) {
continue
} else if char == '#' {
break
} else {
s.err = extraCharError(s.line)
return false
}
}
}
}
// Store key and value
var bufKey, bufValue, bufSep bytes.Buffer
var bytes []byte
for _, r := range key {
bytes = make([]byte, utf8.RuneLen(r))
utf8.EncodeRune(bytes, r)
bufKey.Write(bytes)
}
s.key = bufKey.Bytes()
for _, r := range value {
bytes = make([]byte, utf8.RuneLen(r))
utf8.EncodeRune(bytes, r)
bufValue.Write(bytes)
}
s.value = bufValue.Bytes()
// Store separator character
if s.separator == nil {
for _, r := range separator {
bytes = make([]byte, utf8.RuneLen(r))
utf8.EncodeRune(bytes, r)
bufSep.Write(bytes)
}
s.separator = bufSep.Bytes()
}
return true
}
// Bytes returns the most recents tokens generated by a call to Scan. The
// underlying array may point to data that will be overwritten by a subsequent
// call to Scan. It does no allocation.
func (s *Scanner) Bytes() (key, value []byte) { return s.key, s.value }
// Text returns the most recents tokens generated by a call to Scan as a newly
// allocated string holding its bytes.
func (s *Scanner) Text() (key, value string) {
return string(s.key), string(s.value)
}
// Err returns the first non-EOF error that was encountered by the Scanner.
func (s *Scanner) Err() error {
if s.err != io.EOF {
return s.err
}
return nil
}
// Separator returns the character/s used to separate the key from the value.
//
// The separator is got in the first call to "Scan()"; if it has not been
// called, this makes it explicitly but panics when there is any error.
func (s *Scanner) Separator() []byte {
if s.separator == nil {
if found := s.Scan(); !found {
if err := s.Err(); err != nil {
panic(err)
}
return _DEF_SEPARATOR
}
}
return s.separator
}
// == Utility
func isValidChar(r rune) bool {
if (r < 'A' || r > 'Z') /*&& r < 'a' && r > 'z'*/ && r != '_' {
return false
}
return true
}