lib: add GVariant datatype and functions
This commit is contained in:
parent
3673107bc4
commit
ac9e44a831
|
@ -16,8 +16,10 @@ rec {
|
|||
entryBefore = d.dagEntryBefore;
|
||||
};
|
||||
|
||||
gvariant = import ./gvariant.nix { inherit lib; };
|
||||
|
||||
strings = import ./strings.nix { inherit lib; };
|
||||
types = import ./types.nix { inherit dag lib; };
|
||||
types = import ./types.nix { inherit dag gvariant lib; };
|
||||
|
||||
shell = import ./shell.nix { inherit lib; };
|
||||
zsh = import ./zsh.nix { inherit lib; };
|
||||
|
|
141
modules/lib/gvariant.nix
Normal file
141
modules/lib/gvariant.nix
Normal file
|
@ -0,0 +1,141 @@
|
|||
# A partial and basic implementation of GVariant formatted strings.
|
||||
#
|
||||
# Note, this API is not considered fully stable and it might therefore
|
||||
# change in backwards incompatible ways without prior notice.
|
||||
|
||||
{ lib }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
mkPrimitive = t: v: {
|
||||
_type = "gvariant";
|
||||
type = t;
|
||||
value = v;
|
||||
__toString = self: "@${self.type} ${toString self.value}";
|
||||
};
|
||||
|
||||
type = {
|
||||
arrayOf = t: "a${t}";
|
||||
tupleOf = ts: "(${concatStrings ts})";
|
||||
string = "s";
|
||||
boolean = "b";
|
||||
uchar = "y";
|
||||
int16 = "n";
|
||||
uint16 = "q";
|
||||
int32 = "i";
|
||||
uint32 = "u";
|
||||
int64 = "x";
|
||||
uint64 = "t";
|
||||
double = "d";
|
||||
};
|
||||
|
||||
# Returns the GVariant type of a given Nix value. If no type can be
|
||||
# found for the value then the empty string is returned.
|
||||
typeOf = v:
|
||||
with type;
|
||||
if builtins.isBool v then
|
||||
boolean
|
||||
else if builtins.isInt v then
|
||||
int32
|
||||
else if builtins.isFloat v then
|
||||
double
|
||||
else if builtins.isString v then
|
||||
string
|
||||
else if builtins.isList v then
|
||||
let elemType = elemTypeOf v;
|
||||
in if elemType == "" then "" else arrayOf elemType
|
||||
else if builtins.isAttrs v && v ? type then
|
||||
v.type
|
||||
else
|
||||
"";
|
||||
|
||||
elemTypeOf = vs:
|
||||
if builtins.isList vs then
|
||||
if vs == [ ] then "" else typeOf (head vs)
|
||||
else
|
||||
"";
|
||||
|
||||
in rec {
|
||||
|
||||
inherit type typeOf;
|
||||
|
||||
isArray = hasPrefix "a";
|
||||
isTuple = hasPrefix "(";
|
||||
|
||||
# Returns the GVariant value that most closely matches the given Nix
|
||||
# value. If no GVariant value can be found then `null` is returned.
|
||||
#
|
||||
# No support for dictionaries, maybe types, or variants.
|
||||
mkValue = v:
|
||||
if builtins.isBool v then
|
||||
mkBoolean v
|
||||
else if builtins.isInt v then
|
||||
mkInt32 v
|
||||
else if builtins.isFloat v then
|
||||
mkDouble v
|
||||
else if builtins.isString v then
|
||||
mkString v
|
||||
else if builtins.isList v then
|
||||
if v == [ ] then mkArray type.string [ ] else mkArray (elemTypeOf v) v
|
||||
else if builtins.isAttrs v && (v._type or "") == "gvariant" then
|
||||
v
|
||||
else
|
||||
null;
|
||||
|
||||
mkArray = elemType: elems:
|
||||
mkPrimitive (type.arrayOf elemType) (map mkValue elems) // {
|
||||
__toString = self:
|
||||
"@${self.type} [${concatMapStringsSep "," toString self.value}]";
|
||||
};
|
||||
|
||||
mkEmptyArray = elemType: mkArray elemType [ ];
|
||||
|
||||
mkTuple = elems:
|
||||
let
|
||||
gvarElems = map mkValue elems;
|
||||
tupleType = type.tupleOf (map (e: e.type) gvarElems);
|
||||
in mkPrimitive tupleType gvarElems // {
|
||||
__toString = self:
|
||||
"@${self.type} (${concatMapStringsSep "," toString self.value})";
|
||||
};
|
||||
|
||||
mkBoolean = v:
|
||||
mkPrimitive type.boolean v // {
|
||||
__toString = self: if self.value then "true" else "false";
|
||||
};
|
||||
|
||||
mkString = v:
|
||||
mkPrimitive type.string v // {
|
||||
__toString = self: "'${escape [ "'" ] self.value}'";
|
||||
};
|
||||
|
||||
mkObjectpath = v:
|
||||
mkPrimitive type.string v // {
|
||||
__toString = self: "objectpath '${escape [ "'" ] self.value}'";
|
||||
};
|
||||
|
||||
mkUchar = mkPrimitive type.uchar;
|
||||
|
||||
mkInt16 = mkPrimitive type.int16;
|
||||
|
||||
mkUint16 = mkPrimitive type.uint16;
|
||||
|
||||
mkInt32 = v:
|
||||
mkPrimitive type.int32 v // {
|
||||
__toString = self: toString self.value;
|
||||
};
|
||||
|
||||
mkUint32 = mkPrimitive type.uint32;
|
||||
|
||||
mkInt64 = mkPrimitive type.int64;
|
||||
|
||||
mkUint64 = mkPrimitive type.uint64;
|
||||
|
||||
mkDouble = v:
|
||||
mkPrimitive type.double v // {
|
||||
__toString = self: toString self.value;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
{ lib, dag ? import ./dag.nix { inherit lib; } }:
|
||||
{ lib
|
||||
, dag ? import ./dag.nix { inherit lib; }
|
||||
, gvariant ? import ./gvariant.nix { inherit lib; }
|
||||
}:
|
||||
|
||||
with lib;
|
||||
|
||||
|
@ -6,9 +9,13 @@ let
|
|||
|
||||
typesDag = import ./types-dag.nix { inherit dag lib; };
|
||||
|
||||
# Needed since the type is called gvariant and its merge attribute
|
||||
# must refer back to the type.
|
||||
gvar = gvariant;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
rec {
|
||||
|
||||
inherit (typesDag) dagOf listOrDagOf;
|
||||
|
||||
|
@ -56,4 +63,35 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
gvariant = mkOptionType rec {
|
||||
name = "gvariant";
|
||||
description = "GVariant value";
|
||||
check = v: gvar.mkValue v != null;
|
||||
merge = loc: defs:
|
||||
let
|
||||
vdefs = map (d: d // { value = gvar.mkValue d.value; }) defs;
|
||||
vals = map (d: d.value) vdefs;
|
||||
defTypes = map (x: x.type) vals;
|
||||
sameOrNull = x: y: if x == y then y else null;
|
||||
# A bit naive to just check the first entry…
|
||||
sharedDefType = foldl' sameOrNull (head defTypes) defTypes;
|
||||
allChecked = all (x: check x) vals;
|
||||
in
|
||||
if sharedDefType == null then
|
||||
throw ("Cannot merge definitions of `${showOption loc}' with"
|
||||
+ " mismatched GVariant types given in"
|
||||
+ " ${showFiles (getFiles defs)}.")
|
||||
else if gvar.isArray sharedDefType && allChecked then
|
||||
(types.listOf gvariant).merge
|
||||
loc (map (d: d // { value = d.value.value; } ) vdefs)
|
||||
else if gvar.isTuple sharedDefType && allChecked then
|
||||
mergeOneOption loc defs
|
||||
else if gvar.type.string == sharedDefType && allChecked then
|
||||
types.str.merge loc defs
|
||||
else if gvar.type.double == sharedDefType && allChecked then
|
||||
types.float.merge loc defs
|
||||
else
|
||||
mergeDefaultOption loc defs;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
lib-types-dag-merge = ./dag-merge.nix;
|
||||
lib-types-list-or-dag-merge = ./list-or-dag-merge.nix;
|
||||
|
||||
lib-types-gvariant-merge = ./gvariant-merge.nix;
|
||||
}
|
||||
|
|
55
tests/lib/types/gvariant-merge.nix
Normal file
55
tests/lib/types/gvariant-merge.nix
Normal file
|
@ -0,0 +1,55 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
in {
|
||||
options.examples = mkOption { type = types.attrsOf hm.types.gvariant; };
|
||||
|
||||
config = {
|
||||
examples = with hm.gvariant;
|
||||
mkMerge [
|
||||
{ bool = true; }
|
||||
{ bool = true; }
|
||||
|
||||
{ float = 3.14; }
|
||||
|
||||
{ int = 42; }
|
||||
{ int = 42; }
|
||||
|
||||
{ list = [ "one" ]; }
|
||||
{ list = mkArray type.string [ "two" ]; }
|
||||
|
||||
{ emptyArray1 = [ ]; }
|
||||
{ emptyArray2 = mkEmptyArray type.uint32; }
|
||||
|
||||
{ string = "foo"; }
|
||||
{ string = "foo"; }
|
||||
|
||||
{ tuple = mkTuple [ 1 [ "foo" ] ]; }
|
||||
];
|
||||
|
||||
home.file."result.txt".text = let
|
||||
mkLine = n: v: "${n} = ${toString (hm.gvariant.mkValue v)}";
|
||||
result = concatStringsSep "\n" (mapAttrsToList mkLine config.examples);
|
||||
in result + "\n";
|
||||
|
||||
nmt.script = ''
|
||||
assertFileContent \
|
||||
home-files/result.txt \
|
||||
${
|
||||
pkgs.writeText "expected.txt" ''
|
||||
bool = true
|
||||
emptyArray1 = @as []
|
||||
emptyArray2 = @as []
|
||||
float = 3.140000
|
||||
int = 42
|
||||
list = @as ['one','two']
|
||||
string = 'foo'
|
||||
tuple = @(ias) (1,@as ['foo'])
|
||||
''
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue