Skip to content

Commit

Permalink
nixos/dconf: support generating from attrs
Browse files Browse the repository at this point in the history
  • Loading branch information
linsui committed Jun 10, 2023
1 parent 1760dc9 commit 97da400
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 84 deletions.
1 change: 1 addition & 0 deletions lib/default.nix
Expand Up @@ -41,6 +41,7 @@ let

# serialization
cli = callLibs ./cli.nix;
gvariant = callLibs ./gvariant.nix;
generators = callLibs ./generators.nix;

# misc
Expand Down
73 changes: 60 additions & 13 deletions lib/generators.nix
Expand Up @@ -175,6 +175,41 @@ rec {
+ "\n")
+ (toINI { inherit mkSectionName mkKeyValue listsAsDuplicateKeys; } sections);

/* Apply a function to a recursive attrset to flatten it and take a additional
function that tells it whether to stop recursing. The function takes a reversed
list of the names and returns a list of flattened name value pairs.
Example:
flattenAttrsCond
# never stop recursing
(as: true)
(path: value: [ {name = lib.concatStringsSep "." (lib.reverseList path) } ] )
{ a.b = 1; c.d = 2; }
=> { "a.b" = 1; "c.d" = 2; }
Type:
flattenAttrsCond :: (AttrSet -> Bool) -> ([String] -> a -> AttrSet) -> AttrSet -> AttrSet
*/
flattenAttrsCond =
with lib;
# A function, given the attribute set the recursion is currently at, determine if stop recursion.
cond:
# A function, given a reversed list of attribute names and a value, returns a name value pair.
f:
# Attribute set to flatten.
set:
let
recurse = path:
let
g = name: value:
if isAttrs value && !(cond value)
then (recurse ([ name ] ++ path) value)
else f ([ name ] ++ path) value;
in
set': concatLists (mapAttrsToList g set');
in
foldl recursiveUpdate { } (recurse [ ] set);

/* Generate a git-config file from an attrset.
*
* It has two major differences from the regular INI format:
Expand Down Expand Up @@ -213,21 +248,33 @@ rec {
let mkKeyValue = mkKeyValueDefault { } " = " k;
in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (lib.toList v));

# converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI
gitFlattenAttrs = let
recurse = path: value:
if isAttrs value && !lib.isDerivation value then
lib.mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value
else if length path > 1 then {
${concatStringsSep "." (lib.reverseList (tail path))}.${head path} = value;
} else {
${head path} = value;
};
in attrs: lib.foldl lib.recursiveUpdate { } (lib.flatten (recurse [ ] attrs));

toINI_ = toINI { inherit mkKeyValue mkSectionName; };
in
toINI_ (gitFlattenAttrs attrs);
toINI_ (flattenAttrsCond lib.isDerivation
(path: value: with lib; [{
"${if length path > 1 then
concatStringsSep "." (reverseList (tail path))
else head path}"."${head path}" = value;
}]) attrs);

# mkKeyValueDefault wrapper that handles dconf INI quirks.
# The main differences of the format is that it requires strings to be quoted.
mkDconfKeyValue = mkKeyValueDefault { mkValueString = v: toString (lib.gvariant.mkValue v); } "=";

# Generates INI in dconf keyfile style.
toDconfINI = attrs: toINI { mkKeyValue = mkDconfKeyValue; } (
flattenAttrsCond lib.gvariant.isGVariant
(path: value: with lib; [{
"${if length path > 1 then
concatStringsSep "/" (reverseList (tail path))
else head path}"."${head path}" = value;
}])
attrs);

# Generates dconf locks file. Each key is a line.
toDconfLocks = with lib; attrs: concatStringsSep "\n" (attrNames (flattenAttrsCond isBool
(path: value: optionals value [{ "/${concatStringsSep "/" (reverseList path)}" = value; }])
attrs));

/* Generates JSON from an arbitrary (non-function) value.
* For more information see the documentation of the builtin.
Expand Down
181 changes: 181 additions & 0 deletions lib/gvariant.nix
@@ -0,0 +1,181 @@
# This file is taken from https://github.com/nix-community/home-manager
# Copyright (c) 2017-2022 Home Manager contributors
#
# 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 }:

let
inherit (lib)
concatMapStringsSep concatStrings escape hasPrefix head replaceStrings;

mkPrimitive = t: v: {
_type = "gvariant";
type = t;
value = v;
__toString = self: "@${self.type} ${toString self.value}";
};

type = {
arrayOf = t: "a${t}";
maybeOf = t: "m${t}";
tupleOf = ts: "(${concatStrings ts})";
dictionaryEntryOf = ts: "{${concatStrings ts}}";
string = "s";
boolean = "b";
uchar = "y";
int16 = "n";
uint16 = "q";
int32 = "i";
uint32 = "u";
int64 = "x";
uint64 = "t";
double = "d";
variant = "v";
};

# 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
"";

mkMaybe = elemType: elem:
mkPrimitive (type.maybeOf elemType) elem // {
__toString = self:
if self.value == null then
"@${self.type} nothing"
else
"just ${toString self.value}";
};

in
rec {

inherit type typeOf;

isGVariant = v: v._type or "" == "gvariant";

isArray = hasPrefix "a";
isDictionaryEntry = hasPrefix "{";
isMaybe = hasPrefix "m";
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.

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 [ ];

mkVariant = elem:
let gvarElem = mkValue elem;
in mkPrimitive type.variant gvarElem // {
__toString = self: "@${self.type} <${toString self.value}>";
};

mkDictionaryEntry = elems:
let
gvarElems = map mkValue elems;
dictionaryType = type.dictionaryEntryOf (map (e: e.type) gvarElems);
in
mkPrimitive dictionaryType gvarElems // {
__toString = self:
"@${self.type} {${concatMapStringsSep "," toString self.value}}";
};

mkNothing = elemType: mkMaybe elemType null;

mkJust = elem: let gvarElem = mkValue elem; in mkMaybe gvarElem.type gvarElem;

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:
let sanitize = s: replaceStrings [ "\n" ] [ "\\n" ] (escape [ "'" "\\" ] s);
in mkPrimitive type.string v // {
__toString = self: "'${sanitize 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;
};
}

0 comments on commit 97da400

Please sign in to comment.