Added value parsing for key/value files

This commit is contained in:
Sasha Koshka 2024-04-27 17:07:41 -04:00
parent aba688f2d2
commit 0e0fbeaa88
1 changed files with 142 additions and 0 deletions

View File

@ -14,6 +14,8 @@ package keyValue
import "io"
import "bufio"
import "strings"
import "strconv"
import "unicode"
import "git.tebibyte.media/tomo/xdg/locale"
type keyValueError string
@ -42,6 +44,16 @@ const (
ErrNoDefaultValue = keyValueError("no default value")
// ErrEntryOutsideGroup indicates that an entry was found.
ErrEntryOutsideGroup = keyValueError("entry outside group")
// ErrUnsupportedEscape indicates that an unsupported escape sequence
// was found.
ErrUnsupportedEscape = keyValueError("unsupported escape")
// ErrStringNotASCII indicates that a string or iconstring value was not
// found to contain valid ASCII text.
ErrStringNotASCII = keyValueError("string not ascii")
// ErrBooleanNotTrueOrFalse indicates that a boolean value was not found
// to equal "true" or "false".
ErrBooleanNotTrueOrFalse = keyValueError("boolean not true or false")
)
// Parse parses a key/value file from a Reader.
@ -216,3 +228,133 @@ func (entry Entry) Localize (locale locale.Locale) string {
}
// TODO have functions to parse/validate all data types
// ParseString parses a value of type string.
// Values of type string may contain all ASCII characters except for control
// characters.
// The escape sequences \s, \n, \t, \r, and \\ are supported, meaning ASCII
// space, newline, tab, carriage return, and backslash, respectively.
func ParseString (value string) (string, error) {
value, err := escapeString(value)
if err != nil { return "", err }
if !isAsciiText(value) { return "", ErrStringNotASCII }
return value, nil
}
// ParseLocaleString parses a value of type localestring.
// Values of type localestring are user displayable, and are encoded in UTF-8.
// The escape sequences \s, \n, \t, \r, and \\ are supported, meaning ASCII
// space, newline, tab, carriage return, and backslash, respectively.
func ParseLocaleString (value string) (string, error) {
return value, nil
}
// ParseIconString parses a value of type iconstring.
// Values of type iconstring are the names of icons; these may be absolute
// paths, or symbolic names for icons located using the algorithm described in
// the Icon Theme Specification. Such values are not user-displayable, and are
// encoded in UTF-8.
// The escape sequences \s, \n, \t, \r, and \\ are supported, meaning ASCII
// space, newline, tab, carriage return, and backslash, respectively.
func ParseIconString (value string) (string, error) {
value, err := escapeString(value)
if err != nil { return "", err }
if !isAsciiText(value) { return "", ErrStringNotASCII }
return value, nil
}
// ParseBoolean parses a value of type boolean.
// Values of type boolean must either be the string true or false.
func ParseBoolean (value string) (bool, error) {
if value == "true" { return true, nil }
if value == "false" { return false, nil }
return false, ErrBooleanNotTrueOrFalse
}
// ParseNumeric parses a value of type numeric.
// Values of type numeric must be a valid floating point number as recognized by
// the %f specifier for scanf in the C locale.
func ParseNumeric (value string) (float64, error) {
// TODO ensure this is compliant
return strconv.ParseFloat(value, 64)
}
// ParseMultiple parses multiple of a value type. Any value parsing function can
// be specified. The multiple values should be separated by a semicolon and the
// input string may be optionally terminated by a semicolon. Trailing empty
// strings must always be terminated with a semicolon. Semicolons in these
// values need to be escaped using \;.
func ParseMultiple[T any] (parser func (string) (T, error), value string) ([]T, error) {
values := []T { }
builder := strings.Builder { }
newValue := func () error {
value, err := parser(builder.String())
if err != nil { return err }
values = append(values, value)
builder.Reset()
return nil
}
backslash := false
for _, char := range value {
switch char {
case '\\':
backslash = true
case ';':
if backslash {
builder.WriteRune(';')
backslash = false
} else {
err := newValue()
if err != nil { return nil, err }
}
default:
if backslash {
builder.WriteRune('\\')
backslash = false
}
builder.WriteRune(char)
}
}
if backslash {
builder.WriteRune('\\')
}
if builder.Len() > 0 {
err := newValue()
if err != nil { return nil, err }
}
return values, nil
}
func isAsciiText (value string) bool {
for _, char := range value {
// must be an ascii character that isn't a control character.
if char <= 0x1F || char > unicode.MaxASCII {
return false
}
}
return true
}
func escapeString (value string) (string, error) {
builder := strings.Builder { }
backslash := false
for _, char := range value {
if char == '\\' { backslash = true; continue }
if backslash {
switch char {
case 's': builder.WriteRune(' ')
case 'n': builder.WriteRune('\n')
case 't': builder.WriteRune('\t')
case 'r': builder.WriteRune('\r')
case '\\': builder.WriteRune('\\')
default: return "", ErrUnsupportedEscape
}
} else {
builder.WriteRune(char)
}
}
return builder.String(), nil
}