winreg encoder-decoder implementation

This commit is contained in:
jangko 2020-10-21 16:07:25 +07:00 committed by zah
parent 0df747294e
commit 4304251b09
5 changed files with 140 additions and 17 deletions

View File

@ -7,6 +7,7 @@ type
WinregReader* = object WinregReader* = object
hKey: HKEY hKey: HKEY
path: string path: string
key: seq[string]
WinregReaderError* = object of WinregError WinregReaderError* = object of WinregError
@ -29,23 +30,54 @@ proc init*(T: type WinregReader,
result.hKey = hKey result.hKey = hKey
result.path = path result.path = path
template getUnderlyingType*[T](_: Option[T]): untyped = T
proc readValue*[T](r: var WinregReader, value: var T) proc readValue*[T](r: var WinregReader, value: var T)
{.raises: [SerializationError, IOError, Defect].} = {.raises: [SerializationError, IOError, Defect].} =
mixin readValue mixin readValue
# TODO: reduce allocation
when T is (SomePrimitives or range): when T is (SomePrimitives or range or string):
getValue(w.hKey, w.path, w.key, value) let path = constructPath(r.path, r.key)
discard getValue(r.hKey, path, r.key[^1], value)
elif T is Option:
let path = constructPath(r.path, r.key)
var outVal: getUnderlyingType(value)
if getValue(r.hKey, path, r.key[^1], outVal):
value = some(outVal)
elif T is (seq or array): elif T is (seq or array):
when uTypeIsPrimitives(T): when uTypeIsPrimitives(T):
getValue(w.hKey, w.path, w.key, value) let path = constructPath(r.path, r.key)
discard getValue(r.hKey, path, r.key[^1], value)
elif uTypeIsRecord(T): elif uTypeIsRecord(T):
# TODO: reduce allocation let key = r.key[^1]
discard for i in 0..<value.len:
r.key[^1] = key & $i
r.readValue(value[i])
else: else:
const typeName = typetraits.name(T) const typeName = typetraits.name(T)
{.fatal: "Failed to convert from Winreg array an unsupported type: " & typeName.} {.fatal: "Failed to convert from Winreg array an unsupported type: " & typeName.}
elif T is (object or tuple): elif T is (object or tuple):
discard type T = type(value)
when T.totalSerializedFields > 0:
let fields = T.fieldReadersTable(WinregReader)
var expectedFieldPos = 0
r.key.add ""
value.enumInstanceSerializedFields(fieldName, field):
type FieldType = type field
when T is tuple:
r.key[^1] = $expectedFieldPos
var reader = fields[][expectedFieldPos].reader
expectedFieldPos += 1
else:
r.key[^1] = fieldName
var reader = findFieldReader(fields[], fieldName, expectedFieldPos)
if reader != nil:
reader(value, r)
discard r.key.pop()
else: else:
const typeName = typetraits.name(T) const typeName = typetraits.name(T)
{.fatal: "Failed to convert from Winreg an unsupported type: " & typeName.} {.fatal: "Failed to convert from Winreg an unsupported type: " & typeName.}

View File

@ -150,3 +150,17 @@ template uTypeIsRecord*[N, T](_: type array[N, T]): bool =
true true
else: else:
false false
func constructPath*(root: string, keys: openArray[string]): string =
if keys.len <= 1:
return root
var size = root.len + 1
for i in 0..<keys.len-1:
inc(size, keys[i].len + 1)
result = newStringOfCap(size)
result.add root
result. add '\\'
for i in 0..<keys.len-1:
result.add keys[i]
if i < keys.len-2:
result. add '\\'

View File

@ -26,9 +26,9 @@ template decode*(_: type Winreg,
reader.readValue(RecordType) reader.readValue(RecordType)
template encode*(_: type Winreg, template encode*(_: type Winreg,
value: auto,
hKey: HKEY, hKey: HKEY,
path: string, path: string,
value: auto,
params: varargs[untyped]) = params: varargs[untyped]) =
mixin init, WriterType, writeValue mixin init, WriterType, writeValue
@ -42,6 +42,9 @@ template loadFile*(_: type Winreg,
params: varargs[untyped]): auto = params: varargs[untyped]): auto =
mixin init, ReaderType, readValue mixin init, ReaderType, readValue
# filename should be a Windows Registry path
# such as "HKEY_CLASSES_ROOT\\SOFTWARE\\Nimbus"
# or "HKCU\\SOFTWARE\\Nimbus"
let (hKey, path) = parseWinregPath(filename) let (hKey, path) = parseWinregPath(filename)
var reader = unpackArgs(init, [WinregReader, hKey, path, params]) var reader = unpackArgs(init, [WinregReader, hKey, path, params])
reader.readValue(RecordType) reader.readValue(RecordType)
@ -49,6 +52,7 @@ template loadFile*(_: type Winreg,
template saveFile*(Format: type, filename: string, value: auto, params: varargs[untyped]) = template saveFile*(Format: type, filename: string, value: auto, params: varargs[untyped]) =
mixin init, WriterType, writeValue mixin init, WriterType, writeValue
# filename should be a Windows Registry path
let (hKey, path) = parseWinregPath(filename) let (hKey, path) = parseWinregPath(filename)
var writer = unpackArgs(init, [WinregWriter, hKey, path, params]) var writer = unpackArgs(init, [WinregWriter, hKey, path, params])
writer.writeValue(value) writer.writeValue(value)

View File

@ -7,7 +7,7 @@ type
WinregWriter* = object WinregWriter* = object
hKey: HKEY hKey: HKEY
path: string path: string
key: string key: seq[string]
proc init*(T: type WinregWriter, proc init*(T: type WinregWriter,
hKey: HKEY, path: string): T = hKey: HKEY, path: string): T =
@ -16,20 +16,34 @@ proc init*(T: type WinregWriter,
proc writeValue*(w: var WinregWriter, value: auto) = proc writeValue*(w: var WinregWriter, value: auto) =
mixin enumInstanceSerializedFields, writeValue, writeFieldIMPL mixin enumInstanceSerializedFields, writeValue, writeFieldIMPL
# TODO: reduce allocation
when value is (SomePrimitives or range): when value is (SomePrimitives or range or string):
setValue(w.hKey, w.path, w.key, value) let path = constructPath(w.path, w.key)
discard setValue(w.hKey, path, w.key[^1], value)
elif value is Option:
if value.isSome:
w.writeValue value.get
elif value is (seq or array or openArray): elif value is (seq or array or openArray):
when uTypeIsPrimitives(type value): when uTypeIsPrimitives(type value):
setValue(w.hKey, w.path, w.key, value) let path = constructPath(w.path, w.key)
discard setValue(w.hKey, path, w.key[^1], value)
elif uTypeIsRecord(type value): elif uTypeIsRecord(type value):
# TODO: reduce allocation let key = w.key[^1]
discard for i in 0..<value.len:
w.key[^1] = key & $i
w.writeValue(value[i])
else: else:
const typeName = typetraits.name(value.type) const typeName = typetraits.name(value.type)
{.fatal: "Failed to convert to Winreg array an unsupported type: " & typeName.} {.fatal: "Failed to convert to Winreg array an unsupported type: " & typeName.}
elif value is (object or tuple): elif value is (object or tuple):
discard type RecordType = type value
w.key.add ""
value.enumInstanceSerializedFields(fieldName, field):
type FieldType = type field
w.key[^1] = fieldName
w.writeFieldIMPL(FieldTag[RecordType, fieldName, FieldType], field, value)
discard w.key.pop()
else: else:
const typeName = typetraits.name(value.type) const typeName = typetraits.name(value.type)
{.fatal: "Failed to convert to Winreg an unsupported type: " & typeName.} {.fatal: "Failed to convert to Winreg an unsupported type: " & typeName.}

View File

@ -1,5 +1,5 @@
import import
unittest, unittest, options,
../confutils/winreg/winreg_serialization ../confutils/winreg/winreg_serialization
type type
@ -18,7 +18,7 @@ template readWrite(key: string, val: typed) =
check ok == true check ok == true
check outVal == val check outVal == val
proc testWinregUtils() = proc testUtils() =
suite "winreg utils test suite": suite "winreg utils test suite":
readWrite("some number", 123'u32) readWrite("some number", 123'u32)
readWrite("some number 64", 123'u64) readWrite("some number 64", 123'u64)
@ -36,4 +36,63 @@ proc testWinregUtils() =
check hKey == HKCR check hKey == HKCR
check path == commonPath check path == commonPath
testWinregUtils() proc testEncoder() =
type
Class = enum
Truck
MPV
SUV
Fuel = enum
Gasoline
Diesel
Engine = object
cylinder: int
valve: int16
fuel: Fuel
Suspension = object
dist: int
length: int
Vehicle = object
name: string
color: int
class: Class
engine: Engine
wheel: int
suspension: array[3, Suspension]
door: array[4, int]
antennae: Option[int]
bumper: Option[string]
suite "winreg encoder test suite":
test "basic encoder and decoder":
let v = Vehicle(
name: "buggy",
color: 213,
class: MPV,
engine: Engine(
cylinder: 3,
valve: 2,
fuel: Diesel
),
wheel: 6,
door: [1,2,3,4],
suspension: [
Suspension(dist: 1, length: 5),
Suspension(dist: 2, length: 6),
Suspension(dist: 3, length: 7)
],
bumper: some("Chromium")
)
Winreg.encode(HKCU, commonPath, v)
let x = Winreg.decode(HKCU, commonPath, Vehicle)
check x == v
check x.antennae.isNone
check x.bumper.get() == "Chromium"
testUtils()
testEncoder()