mirror of
https://github.com/Luzifer/password.git
synced 2024-11-14 20:22:47 +00:00
356 lines
7.6 KiB
Go
356 lines
7.6 KiB
Go
// 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
|
|
}
|