7c8bf29df3
Replaces the list attr -> KDL conversion logic with a more flexible approach, allowing for multiple nodes with the same name in a scope. This unfortunately also breaks the existing JSON-in-KDL semantics in favor of ergonomics. As far as I can tell though, zellij is the only program using it, and it doesn't accept JiK anyway. For example, the following KDL was previously impossible to generate, since nix attrs were mapped 1:1 to KDL nodes: ``` resize { bind "k" "Up" { Resize "Increase Up"; } bind "j" "Down" { Resize "Increase Down"; } } ``` Now, this can be achieved with the nix expression: ``` resize.bind = [ { _args = ["k" "Up"]; Resize = "Increase Up"; } { _args = ["j" "Down"]; Resize = "Increase Down"; } ]; ``` which would previously have generated the not-very-useful: ``` resize { bind { - "k" "Up" { Resize "Increase Up"; } - "j" "Down" { Resize "Increase Down"; } } } ``` which, in turn, can now be generated via: ``` resize.bind."-" = [ { _args = ["k" "Up"]; Resize = "Increase Up"; } { _args = ["j" "Down"]; Resize = "Increase Down"; } ]; ```
194 lines
6.8 KiB
Nix
194 lines
6.8 KiB
Nix
{ lib }:
|
|
|
|
{
|
|
toKDL = { }:
|
|
let
|
|
inherit (lib) concatStringsSep splitString 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 -> 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);
|
|
|
|
# Attrset Conversion
|
|
# String -> AttrsOf Anything -> String
|
|
convertAttrsToKDL = name: attrs:
|
|
let
|
|
optArgsString = lib.optionalString (attrs ? "_args")
|
|
(lib.pipe attrs._args [
|
|
(map literalValueToString)
|
|
(lib.concatStringsSep " ")
|
|
(s: s + " ")
|
|
]);
|
|
|
|
optPropsString = lib.optionalString (attrs ? "_props")
|
|
(lib.pipe attrs._props [
|
|
(lib.mapAttrsToList
|
|
(name: value: "${name}=${literalValueToString value}"))
|
|
(lib.concatStringsSep " ")
|
|
(s: s + " ")
|
|
]);
|
|
|
|
children =
|
|
lib.filterAttrs (name: _: !(elem name [ "_args" "_props" ])) attrs;
|
|
in ''
|
|
${name} ${optArgsString}${optPropsString}{
|
|
${indentStrings (mapAttrsToList convertAttributeToKDL children)}
|
|
}'';
|
|
|
|
# List Conversion
|
|
# String -> ListOf (OneOf [Int Float String Bool Null]) -> String
|
|
convertListOfFlatAttrsToKDL = name: list:
|
|
let flatElements = map literalValueToString list;
|
|
in "${name} ${concatStringsSep " " flatElements}";
|
|
|
|
# String -> ListOf Anything -> String
|
|
convertListOfNonFlatAttrsToKDL = name: list:
|
|
"${concatStringsSep "\n" (map (x: convertAttributeToKDL name x) list)}";
|
|
|
|
# String -> ListOf Anything -> String
|
|
convertListToKDL = name: list:
|
|
let elementsAreFlat = !any (el: elem (typeOf el) [ "list" "set" ]) list;
|
|
in if elementsAreFlat then
|
|
convertListOfFlatAttrsToKDL name list
|
|
else
|
|
convertListOfNonFlatAttrsToKDL name list;
|
|
|
|
# Combined Conversion
|
|
# String -> Anything -> String
|
|
convertAttributeToKDL = name: value:
|
|
let vType = typeOf value;
|
|
in if elem vType [ "int" "float" "bool" "null" "string" ] then
|
|
"${name} ${literalValueToString value}"
|
|
else if vType == "set" then
|
|
convertAttrsToKDL name value
|
|
else if vType == "list" then
|
|
convertListToKDL name value
|
|
else
|
|
throw ''
|
|
Cannot convert type `(${typeOf value})` to KDL:
|
|
${name} = ${toString value}
|
|
'';
|
|
in attrs: ''
|
|
${concatStringsSep "\n" (mapAttrsToList convertAttributeToKDL attrs)}
|
|
'';
|
|
|
|
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)}
|
|
'';
|
|
}
|