diff options
| author | 2026-03-06 10:52:02 +0800 | |
|---|---|---|
| committer | 2026-03-06 10:53:37 +0800 | |
| commit | f36918966727be99bfe9d461059269f36f92058a (patch) | |
| tree | ff2c6545d369a74c210574f821298181307c701a /config/typed.go | |
| parent | objecttype: Split files (diff) | |
| signature | No signature | |
config: Split files
Diffstat (limited to 'config/typed.go')
| -rw-r--r-- | config/typed.go | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/config/typed.go b/config/typed.go new file mode 100644 index 00000000..39eeb767 --- /dev/null +++ b/config/typed.go @@ -0,0 +1,170 @@ +package config + +import ( + "errors" + "fmt" + "math" + "strconv" + "strings" + + "codeberg.org/lindenii/furgit/internal/intconv" +) + +// ValueKind describes the presence and form of a config value. +type ValueKind uint8 + +const ( + // ValueMissing means the queried key does not exist. + ValueMissing ValueKind = iota + // ValueValueless means the key exists but has no "= <value>" part. + ValueValueless + // ValueString means the key exists and has an explicit value (possibly ""). + ValueString +) + +func isValidSection(s string) bool { + if len(s) == 0 { + return false + } + + for i := range len(s) { + ch := s[i] + if !isLetter(ch) && !isDigit(ch) && ch != '-' && ch != '.' { + return false + } + } + + return true +} + +func isKeyChar(ch byte) bool { + return isLetter(ch) || isDigit(ch) || ch == '-' +} + +func parseBool(value string) (bool, error) { + switch { + case strings.EqualFold(value, "true"), + strings.EqualFold(value, "yes"), + strings.EqualFold(value, "on"): + return true, nil + case strings.EqualFold(value, "false"), + strings.EqualFold(value, "no"), + strings.EqualFold(value, "off"), + value == "": + return false, nil + } + + n, err := parseInt32(value) + if err != nil { + return false, fmt.Errorf("invalid boolean value %q", value) + } + + return n != 0, nil +} + +func parseInt32(value string) (int32, error) { + n64, err := parseInt64WithMax(value, math.MaxInt32) + if err != nil { + return 0, err + } + + return intconv.Int64ToInt32(n64) +} + +func parseInt(value string) (int, error) { + n64, err := parseInt64WithMax(value, int64(int(^uint(0)>>1))) + if err != nil { + return 0, err + } + + return int(n64), nil +} + +func parseInt64(value string) (int64, error) { + return parseInt64WithMax(value, int64(^uint64(0)>>1)) +} + +func parseInt64WithMax(value string, maxValue int64) (int64, error) { + if value == "" { + return 0, errors.New("empty value") + } + + trimmed := strings.TrimLeft(value, " \t\n\r\f\v") + if trimmed == "" { + return 0, errors.New("empty value") + } + + numPart := trimmed + factor := int64(1) + + if last := trimmed[len(trimmed)-1]; last == 'k' || last == 'K' || last == 'm' || last == 'M' || last == 'g' || last == 'G' { + switch toLower(last) { + case 'k': + factor = 1024 + case 'm': + factor = 1024 * 1024 + case 'g': + factor = 1024 * 1024 * 1024 + } + + numPart = trimmed[:len(trimmed)-1] + } + + if numPart == "" { + return 0, errors.New("missing integer value") + } + + n, err := strconv.ParseInt(numPart, 0, 64) + if err != nil { + return 0, err + } + + intMax := maxValue + intMin := -maxValue - 1 + + if n > 0 && n > intMax/factor { + return 0, errors.New("integer overflow") + } + + if n < 0 && n < intMin/factor { + return 0, errors.New("integer overflow") + } + + n *= factor + + return n, nil +} + +func truncateAtNUL(value string) string { + for i := range len(value) { + if value[i] == 0 { + return value[:i] + } + } + + return value +} + +func isSpace(ch byte) bool { + return ch == ' ' || ch == '\t' +} + +func isWhitespace(ch byte) bool { + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f' +} + +func isLetter(ch byte) bool { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') +} + +func isDigit(ch byte) bool { + return ch >= '0' && ch <= '9' +} + +func toLower(ch byte) byte { + if ch >= 'A' && ch <= 'Z' { + return ch + ('a' - 'A') + } + + return ch +} |
