1
0
Fork 0
mirror of https://github.com/nix-community/home-manager synced 2024-12-03 16:39:46 +01:00
home-manager/modules/lib/generators.nix
2024-04-16 13:59:03 -04:00

186 lines
6.2 KiB
Nix

{ lib }:
{
toKDL = { }:
let
inherit (lib) concatStringsSep;
inherit (builtins) typeOf replaceStrings elem;
# KDL Spec Summary
# Document -> Node[]
# Node -> {[Type] NodeName [Args] [Properties] [Children]}
# Type -> Ident
# NodeName -> Ident
# Args -> Value[] # Note: ordered
# Properties -> map[Ident]Value # Note: Unordered
# Children -> Node[] # Note: ordered
# Value -> String | Number | Bool | Null
# ListOf String -> String
indentStrings = let
# Although the input of this function is a list of strings,
# the strings themselves *will* contain newlines, so you need
# to normalize the list by joining and resplitting them.
unlines = lib.splitString "\n";
lines = lib.concatStringsSep "\n";
indentAll = lines: concatStringsSep "\n" (map (x: " " + x) lines);
in stringsWithNewlines: indentAll (unlines (lines stringsWithNewlines));
# String -> String
sanitizeString = replaceStrings [ "\n" ''"'' ] [ "\\n" ''\"'' ];
# OneOf [Int Float String Bool Null] -> String
literalValueToString = element:
lib.throwIfNot
(elem (typeOf element) [ "int" "float" "string" "bool" "null" ])
"Cannot convert value of type ${typeOf element} to KDL literal."
(if typeOf element == "null" then
"null"
else if element == false then
"false"
else if element == true then
"true"
else if typeOf element == "string" then
''"${sanitizeString element}"''
else
toString element);
# Node Attrset Conversion
# AttrsOf Anything -> String
attrsToKDLNode = attrs:
let
optType = lib.optionalString (attrs ? "type") attrs.type;
name = attrs.name;
optArgsString = lib.optionalString (attrs ? "args")
(lib.pipe attrs.args [
(a: if typeOf a == "list" then a else [ a ])
(map literalValueToString)
(lib.concatStringsSep " ")
]);
optPropsString = lib.optionalString (attrs ? "props")
(lib.pipe attrs.props [
(lib.mapAttrsToList
(name: value: "${name}=${literalValueToString value}"))
(lib.concatStringsSep " ")
]);
optChildren = lib.optionalString (attrs ? "children")
(lib.pipe attrs.children [
(a: if typeOf a == "list" then a else [ a ])
(map attrsToKDLNode)
(s:
lib.optionalString (builtins.length s > 0) ''
{
${indentStrings s}
}'')
]);
in lib.concatStringsSep " " (lib.filter (s: s != "") [
optType
name
optArgsString
optPropsString
optChildren
]);
in nodes: ''
${concatStringsSep "\n" (map attrsToKDLNode nodes)}
'';
toSCFG = { }:
let
inherit (lib) concatStringsSep mapAttrsToList any;
inherit (builtins) typeOf replaceStrings elem;
# ListOf String -> String
indentStrings = let
# Although the input of this function is a list of strings,
# the strings themselves *will* contain newlines, so you need
# to normalize the list by joining and resplitting them.
unlines = lib.splitString "\n";
lines = lib.concatStringsSep "\n";
indentAll = lines: concatStringsSep "\n" (map (x: " " + x) lines);
in stringsWithNewlines: indentAll (unlines (lines stringsWithNewlines));
# String -> Bool
specialChars = s:
any (char: elem char (reserved ++ [ " " "'" "{" "}" ]))
(lib.stringToCharacters s);
# String -> String
sanitizeString =
replaceStrings reserved [ ''\"'' "\\\\" "\\r" "\\n" "\\t" ];
reserved = [ ''"'' "\\" "\r" "\n" " " ];
# OneOf [Int Float String Bool] -> String
literalValueToString = element:
lib.throwIfNot (elem (typeOf element) [ "int" "float" "string" "bool" ])
"Cannot convert value of type ${typeOf element} to SCFG literal."
(if element == false then
"false"
else if element == true then
"true"
else if typeOf element == "string" then
if element == "" || specialChars element then
''"${sanitizeString element}"''
else
element
else
toString element);
# Bool -> ListOf (OneOf [Int Float String Bool]) -> String
toOptParamsString = cond: list:
lib.optionalString (cond) (lib.pipe list [
(map literalValueToString)
(concatStringsSep " ")
(s: " " + s)
]);
# Attrset Conversion
# String -> AttrsOf Anything -> String
convertAttrsToSCFG = name: attrs:
let
optParamsString = toOptParamsString (attrs ? "_params") attrs._params;
in ''
${name}${optParamsString} {
${indentStrings (convertToAttrsSCFG' attrs)}
}'';
# Attrset Conversion
# AttrsOf Anything -> ListOf String
convertToAttrsSCFG' = attrs:
mapAttrsToList convertAttributeToSCFG
(lib.filterAttrs (name: val: !isNull val && name != "_params") attrs);
# List Conversion
# String -> ListOf (OneOf [Int Float String Bool]) -> String
convertListOfFlatAttrsToSCFG = name: list:
let optParamsString = toOptParamsString (list != [ ]) list;
in "${name}${optParamsString}";
# Combined Conversion
# String -> Anything -> String
convertAttributeToSCFG = name: value:
lib.throwIf (name == "") "Directive must not be empty"
(let vType = typeOf value;
in if elem vType [ "int" "float" "bool" "string" ] then
"${name} ${literalValueToString value}"
else if vType == "set" then
convertAttrsToSCFG name value
else if vType == "list" then
convertListOfFlatAttrsToSCFG name value
else
throw ''
Cannot convert type `(${typeOf value})` to SCFG:
${name} = ${toString value}
'');
in attrs:
lib.optionalString (attrs != { }) ''
${concatStringsSep "\n" (convertToAttrsSCFG' attrs)}
'';
}