store: added ini unmarshaler

This commit is contained in:
RoBaertschi 2025-06-19 15:38:48 +02:00
parent 9fc1649d2a
commit 054473feb3
No known key found for this signature in database
2 changed files with 300 additions and 12 deletions

View File

@ -1,11 +1,12 @@
package cmd
import "../deamon"
// import "../deamon"
import "../store"
import "core:os"
import "core:log"
import "core:hash/xxhash"
import "core:encoding/base64"
// import "core:hash/xxhash"
// import "core:encoding/base64"
Options :: struct {
deamon: bool,
@ -17,12 +18,15 @@ main :: proc() {
d, _ := os.read_entire_file_or_err(os.args[1])
defer delete(d)
hash := xxhash.XXH3_128(d)
hash_bytes := transmute([size_of(hash)]u8)hash
data, _ := base64.encode(hash_bytes[:])
base64.ENC_TABLE
config: store.Config
log.error(store.ini_unmarshal(string(d), &config), config)
log.infof("%q", data)
// hash := xxhash.XXH3_128(d)
// hash_bytes := transmute([size_of(hash)]u8)hash
// data, _ := base64.encode(hash_bytes[:])
// base64.ENC_TABLE
//
// log.infof("%q", data)
//

View File

@ -2,13 +2,91 @@ package store
// Ini Reflection code
import "base:runtime"
import "core:mem"
import "core:fmt"
import "core:time"
import "core:strconv"
import "core:strings"
import "core:reflect"
import "core:encoding/ini"
import "core:encoding/json"
// import "core:encoding/json"
Ini_Missing_Category :: distinct string
Ini_Missing_Field :: distinct string
Ini_Unmarshal_Error :: union {
mem.Allocator_Error,
Ini_Missing_Category,
Ini_Missing_Field,
}
/*
Get the value for a subtag.
This is useful if you need to parse through the `args` tag for a struct field
on a custom type setter or custom flag checker.
Example:
import "core:flags"
import "core:fmt"
subtag_example :: proc() {
args_tag := "precision=3,signed"
precision, has_precision := flags.get_subtag(args_tag, "precision")
signed, is_signed := flags.get_subtag(args_tag, "signed")
fmt.printfln("precision = %q, %t", precision, has_precision)
fmt.printfln("signed = %q, %t", signed, is_signed)
}
Output:
precision = "3", true
signed = "", true
*/
ini_get_subtag :: proc(tag, id: string) -> (value: string, ok: bool) {
// This proc was initially private in `internal_rtti.odin`, but given how
// useful it would be to custom type setters and flag checkers, it lives
// here now.
tag := tag
for subtag in strings.split_iterator(&tag, ",") {
if equals := strings.index_byte(subtag, '='); equals != -1 && id == subtag[:equals] {
return subtag[1 + equals:], true
} else if id == subtag {
return "", true
}
}
return
}
ini_get_subtag_with_type :: proc(tag, subtag: string, $T: typeid) -> (t: T, ok: bool) {
ok = ini_parse_and_set_pointer_by_base_type(&t, ini_get_subtag(tag, subtag) or_return, type_info_of(T))
return
}
@private
ini_rtti_get_struct :: proc(type: ^runtime.Type_Info, named: string = "") -> (base_type: ^runtime.Type_Info, base_struct_type: runtime.Type_Info_Struct, name: string, ok: bool) {
name = named
#partial switch t in type.variant {
case runtime.Type_Info_Struct:
base_type = type
base_struct_type = t
ok = true
case runtime.Type_Info_Named:
name = t.name
return ini_rtti_get_struct(t.base, name)
case:
ok = false
}
return
}
ini_unmarshal :: proc(data: string, v: ^$T, allocator := context.allocator) -> Ini_Unmarshal_Error {
@ -18,10 +96,216 @@ ini_unmarshal :: proc(data: string, v: ^$T, allocator := context.allocator) -> I
fields := reflect.struct_fields_zipped(T)
for field in fields {
base_type, _, _, t_ok := ini_rtti_get_struct(field.type)
if !t_ok {
fmt.panicf(
"INI: Invalid type for field %q on %T, expected a struct but got %v",
field.name,
v^,
field,
)
}
tag, _ := reflect.struct_tag_lookup(field.tag, "ini")
name := ini_get_subtag(tag, "name") or_else field.name
_, required := ini_get_subtag(tag, "required")
category, ok := m[name]
if !ok {
if required {
return Ini_Missing_Category(strings.clone(name, allocator = allocator))
}
}
struct_fields := reflect.struct_fields_zipped(base_type.id)
for struct_field in struct_fields {
struct_field_tag, _ := reflect.struct_tag_lookup(struct_field.tag, "ini")
_, struct_field_required := ini_get_subtag(struct_field_tag, "required")
struct_field_name := ini_get_subtag(struct_field_tag, "name") or_else struct_field.name
default: Maybe(string)
default_ok: bool
default, default_ok = ini_get_subtag_with_type(struct_field_tag, "default", string)
defer if default != nil { delete(default.?) }
if !default_ok {
default = nil
}
value: string
if value, ok = category[struct_field_name]; !ok {
if value, ok = default.?; !ok && struct_field_required {
return Ini_Missing_Field(strings.clone(struct_field_name, allocator = allocator))
}
}
ini_parse_and_set_pointer_by_base_type(cast(rawptr)(uintptr(v)+field.offset+struct_field.offset), value, struct_field.type)
}
}
return nil
}
ini_bool_default :: proc(field: reflect.Struct_Field) {
val, ok := reflect.struct_tag_lookup(field.tag, "ini")
ini_parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info, allocator := context.allocator) -> bool {
#partial switch type_info_variant in type_info.variant {
case runtime.Type_Info_Boolean:
value := strconv.parse_bool(str) or_return
switch type_info.id {
case bool: (cast(^bool)ptr)^ = value
case b8: (cast(^b8) ptr)^ = b8(value)
case b16: (cast(^b16) ptr)^ = b16(value)
case b32: (cast(^b32) ptr)^ = b32(value)
case b64: (cast(^b64) ptr)^ = b64(value)
}
case runtime.Type_Info_String:
if type_info_variant.is_cstring {
cstr_ptr := cast(^cstring)ptr
if cstr_ptr != nil {
delete(cstr_ptr^)
}
cstr_ptr^ = strings.clone_to_cstring(str, allocator = allocator)
} else {
(cast(^string)ptr)^ = strings.clone(str, allocator = allocator)
}
case:
// Others
if type_info.id == time.Duration {
l: int
value, ok := strconv.parse_i64_of_base(str, 10, &l)
if !ok && l <= 0 {
return false
} else if len(str) > l {
ending := str[l]
switch ending {
case 'h': (cast(^time.Duration)ptr)^ = time.Duration(value) * time.Duration( time.Hour)
case 'm': (cast(^time.Duration)ptr)^ = time.Duration(value) * time.Duration(time.Minute)
case 's': (cast(^time.Duration)ptr)^ = time.Duration(value) * time.Duration(time.Second)
case: return false
}
} else if ok {
(cast(^time.Duration)ptr)^ = time.Duration(value)
return true
} else {
return false
}
} else {
return false
}
}
return true
}
ini_bool_default :: proc(field: reflect.Struct_Field, default: string, default_found: string) {
}
import te "core:testing"
@test
ini_parse_and_set_pointer_by_base_type_success :: proc(t: ^te.T) {
test_with_type_info :: proc(t: ^te.T, $T: typeid, str: string, expected: T) {
val: T
te.expect_value(t, ini_parse_and_set_pointer_by_base_type(&val, str, type_info_of(T)), true)
te.expectf(t, val == expected, "expected value of type %T to be %v, but got %v", expected, expected, val)
}
test_with_type_info(t, bool, "true", true)
test_with_type_info(t, b8, "true", true)
test_with_type_info(t, b16, "true", true)
test_with_type_info(t, b32, "true", true)
test_with_type_info(t, b64, "true", true)
test_with_type_info(t, bool, "false", false)
test_with_type_info(t, time.Duration, "10", 10)
test_with_type_info(t, time.Duration, "10h", time.Duration(10 * time.Hour))
test_with_type_info(t, time.Duration, "10m", time.Duration(10 * time.Minute))
test_with_type_info(t, time.Duration, "10s", time.Duration(10 * time.Second))
}
@test
ini_parse_and_set_pointer_by_base_type_fail :: proc(t: ^te.T) {
test_with_type_info :: proc(t: ^te.T, $T: typeid, str: string) {
val: T
te.expect_value(t, ini_parse_and_set_pointer_by_base_type(&val, str, type_info_of(T)), false)
}
test_with_type_info(t, bool, "deez")
test_with_type_info(t, b8, "deez")
test_with_type_info(t, b16, "deez")
test_with_type_info(t, b32, "deez")
test_with_type_info(t, b64, "deez")
test_with_type_info(t, time.Duration, "")
test_with_type_info(t, time.Duration, "10r")
test_with_type_info(t, time.Duration, "10b")
test_with_type_info(t, time.Duration, "10c")
}
@test
ini_unmarshal_test :: proc(t: ^te.T) {
test :: proc(t: ^te.T, $T: typeid, data: string, expected: T, should_fail : Ini_Unmarshal_Error = nil) -> T {
val: T
err := ini_unmarshal(data, &val)
te.expectf(t, reflect.eq(err, should_fail), "Expected %v to be equal to %v", err, should_fail)
if should_fail == nil {
te.expectf(t, reflect.eq(val, expected), "Expected %v to be equal to %v from data %v", val, expected, data)
} else {
#partial switch e in err {
case Ini_Missing_Category:
delete(string(e))
case Ini_Missing_Field:
delete(string(e))
case:
}
}
return val
}
Basic :: struct {
hi: struct {
enable0: bool,
enable1: b8,
enable2: b16,
enable3: b32,
enable4: b64,
},
}
test(t, Basic, "[hi]\n", Basic{})
test(t, Basic, "[hi]\nenable1 = true\nenable4 = false", Basic{hi = {enable1 = true}})
Attributes :: struct {
named: struct {enable_stuff: bool `ini:"name=enable"`} `ini:"name=not_named"`,
required: struct {enable: bool `ini:"required"`} `ini:"required"`,
}
test(t, Attributes, `
[not_named]
enable = true
[required]
enable = true
`, Attributes{named = {enable_stuff = true}, required = {enable = true}})
test(t, Attributes, `
`, Attributes{}, Ini_Missing_Category("required"))
test(t, Attributes, `
[required]
empyt = hi
`, Attributes{}, Ini_Missing_Field("enable"))
Defaults :: struct {
defaults: struct {
enable: bool `ini:"default=true"`,
str: string `ini:"default=hello"`,
duration: time.Duration `ini:"default=4h"`,
},
}
defaults := test(t, Defaults, "", Defaults{defaults = {enable = true, str = "hello", duration = time.Hour * 4}})
delete(defaults.defaults.str)
}