store: added ini unmarshaler
This commit is contained in:
parent
9fc1649d2a
commit
054473feb3
20
cmd/cmd.odin
20
cmd/cmd.odin
@ -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)
|
||||
|
||||
|
||||
//
|
||||
|
290
store/ini.odin
290
store/ini.odin
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
ini_bool_default :: proc(field: reflect.Struct_Field) {
|
||||
val, ok := reflect.struct_tag_lookup(field.tag, "ini")
|
||||
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_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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user