lib: add GVariant datatype and functions

This commit is contained in:
Robert Helgesson 2020-03-09 23:25:23 +01:00
parent 3673107bc4
commit ac9e44a831
No known key found for this signature in database
GPG key ID: 36BDAA14C2797E89
5 changed files with 241 additions and 3 deletions

View file

@ -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
View 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;
};
}

View file

@ -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;
};
}

View file

@ -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;
}

View 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'])
''
}
'';
};
}