Merge pull request #293901 from philiptaron/refactor-lib/generators.nix

lib: use explicit name imports in `lib/generators.nix`
This commit is contained in:
Silvan Mosberger 2024-03-18 19:41:47 +01:00 committed by GitHub
commit c9772eaba0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -14,15 +14,58 @@
* Documentation in the manual, #sec-generators
*/
{ lib }:
with (lib).trivial;
let
libStr = lib.strings;
libAttr = lib.attrsets;
inherit (lib)
addErrorContext
assertMsg
attrNames
concatLists
concatMapStringsSep
concatStrings
concatStringsSep
const
elem
escape
filter
flatten
foldl
functionArgs # Note: not the builtin; considers `__functor` in attrsets.
gvariant
hasInfix
head
id
init
isAttrs
isBool
isDerivation
isFloat
isFunction # Note: not the builtin; considers `__functor` in attrsets.
isInt
isList
isPath
isString
last
length
mapAttrs
mapAttrsToList
optionals
recursiveUpdate
replaceStrings
reverseList
splitString
tail
toList
;
inherit (lib) isFunction;
in
rec {
inherit (lib.strings)
escapeNixIdentifier
floatToString
match
split
toJSON
typeOf
;
## -- HELPER FUNCTIONS & DEFAULTS --
@ -30,13 +73,13 @@ rec {
* The builtin `toString` function has some strange defaults,
* suitable for bash scripts but not much else.
*/
mkValueStringDefault = {}: v: with builtins;
mkValueStringDefault = {}: v:
let err = t: v: abort
("generators.mkValueStringDefault: " +
"${t} not supported: ${toPretty {} v}");
in if isInt v then toString v
# convert derivations to store paths
else if lib.isDerivation v then toString v
else if isDerivation v then toString v
# we default to not quoting strings
else if isString v then v
# isString returns "1", which is not a good default
@ -53,7 +96,7 @@ rec {
# Floats currently can't be converted to precise strings,
# condition warning on nix version once this isn't a problem anymore
# See https://github.com/NixOS/nix/pull/3480
else if isFloat v then libStr.floatToString v
else if isFloat v then floatToString v
else err "this value is" (toString v);
@ -69,7 +112,7 @@ rec {
mkKeyValueDefault = {
mkValueString ? mkValueStringDefault {}
}: sep: k: v:
"${libStr.escape [sep] k}${sep}${mkValueString v}";
"${escape [sep] k}${sep}${mkValueString v}";
## -- FILE FORMAT GENERATORS --
@ -86,9 +129,9 @@ rec {
}:
let mkLine = k: v: indent + mkKeyValue k v + "\n";
mkLines = if listsAsDuplicateKeys
then k: v: map (mkLine k) (if lib.isList v then v else [v])
then k: v: map (mkLine k) (if isList v then v else [v])
else k: v: [ (mkLine k v) ];
in attrs: libStr.concatStrings (lib.concatLists (libAttr.mapAttrsToList mkLines attrs));
in attrs: concatStrings (concatLists (mapAttrsToList mkLines attrs));
/* Generate an INI-style config file from an
@ -113,7 +156,7 @@ rec {
*/
toINI = {
# apply transformations (e.g. escapes) to section names
mkSectionName ? (name: libStr.escape [ "[" "]" ] name),
mkSectionName ? (name: escape [ "[" "]" ] name),
# format a setting line from key and value
mkKeyValue ? mkKeyValueDefault {} "=",
# allow lists as values for duplicate keys
@ -122,8 +165,8 @@ rec {
let
# map function to string for each key val
mapAttrsToStringsSep = sep: mapFn: attrs:
libStr.concatStringsSep sep
(libAttr.mapAttrsToList mapFn attrs);
concatStringsSep sep
(mapAttrsToList mapFn attrs);
mkSection = sectName: sectValues: ''
[${mkSectionName sectName}]
'' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues;
@ -164,7 +207,7 @@ rec {
*/
toINIWithGlobalSection = {
# apply transformations (e.g. escapes) to section names
mkSectionName ? (name: libStr.escape [ "[" "]" ] name),
mkSectionName ? (name: escape [ "[" "]" ] name),
# format a setting line from key and value
mkKeyValue ? mkKeyValueDefault {} "=",
# allow lists as values for duplicate keys
@ -195,12 +238,11 @@ rec {
*> name = "edolstra"
*/
toGitINI = attrs:
with builtins;
let
mkSectionName = name:
let
containsQuote = libStr.hasInfix ''"'' name;
sections = libStr.splitString "." name;
containsQuote = hasInfix ''"'' name;
sections = splitString "." name;
section = head sections;
subsections = tail sections;
subsection = concatStringsSep "." subsections;
@ -220,19 +262,19 @@ rec {
# generation for multiple ini values
mkKeyValue = k: v:
let mkKeyValue = mkKeyValueDefault { inherit mkValueString; } " = " k;
in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (lib.toList v));
in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (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
if isAttrs value && !isDerivation value then
mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value
else if length path > 1 then {
${concatStringsSep "." (lib.reverseList (tail path))}.${head path} = value;
${concatStringsSep "." (reverseList (tail path))}.${head path} = value;
} else {
${head path} = value;
};
in attrs: lib.foldl lib.recursiveUpdate { } (lib.flatten (recurse [ ] attrs));
in attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs));
toINI_ = toINI { inherit mkKeyValue mkSectionName; };
in
@ -240,25 +282,12 @@ rec {
# 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); } "=";
mkDconfKeyValue = mkKeyValueDefault { mkValueString = v: toString (gvariant.mkValue v); } "=";
# Generates INI in dconf keyfile style. See https://help.gnome.org/admin/system-admin-guide/stable/dconf-keyfiles.html.en
# for details.
toDconfINI = toINI { mkKeyValue = mkDconfKeyValue; };
/* Generates JSON from an arbitrary (non-function) value.
* For more information see the documentation of the builtin.
*/
toJSON = {}: builtins.toJSON;
/* YAML has been a strict superset of JSON since 1.2, so we
* use toJSON. Before it only had a few differences referring
* to implicit typing rules, so it should work with older
* parsers as well.
*/
toYAML = toJSON;
withRecursion =
{
/* If this option is not null, the given value will stop evaluating at a certain depth */
@ -266,7 +295,7 @@ rec {
/* If this option is true, an error will be thrown, if a certain given depth is exceeded */
, throwOnDepthLimit ? true
}:
assert builtins.isInt depthLimit;
assert isInt depthLimit;
let
specialAttrs = [
"__functor"
@ -275,7 +304,7 @@ rec {
"__pretty"
];
stepIntoAttr = evalNext: name:
if builtins.elem name specialAttrs
if elem name specialAttrs
then id
else evalNext;
transform = depth:
@ -284,7 +313,7 @@ rec {
then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!"
else const "<unevaluated>"
else id;
mapAny = with builtins; depth: v:
mapAny = depth: v:
let
evalNext = x: mapAny (depth + 1) (transform (depth + 1) x);
in
@ -311,9 +340,8 @@ rec {
indent ? ""
}:
let
go = indent: v: with builtins;
let isPath = v: typeOf v == "path";
introSpace = if multiline then "\n${indent} " else " ";
go = indent: v:
let introSpace = if multiline then "\n${indent} " else " ";
outroSpace = if multiline then "\n${indent}" else " ";
in if isInt v then toString v
# toString loses precision on floats, so we use toJSON instead. This isn't perfect
@ -322,16 +350,16 @@ rec {
else if isFloat v then builtins.toJSON v
else if isString v then
let
lines = filter (v: ! isList v) (builtins.split "\n" v);
escapeSingleline = libStr.escape [ "\\" "\"" "\${" ];
escapeMultiline = libStr.replaceStrings [ "\${" "''" ] [ "''\${" "'''" ];
lines = filter (v: ! isList v) (split "\n" v);
escapeSingleline = escape [ "\\" "\"" "\${" ];
escapeMultiline = replaceStrings [ "\${" "''" ] [ "''\${" "'''" ];
singlelineResult = "\"" + concatStringsSep "\\n" (map escapeSingleline lines) + "\"";
multilineResult = let
escapedLines = map escapeMultiline lines;
# The last line gets a special treatment: if it's empty, '' is on its own line at the "outer"
# indentation level. Otherwise, '' is appended to the last line.
lastLine = lib.last escapedLines;
in "''" + introSpace + concatStringsSep introSpace (lib.init escapedLines)
lastLine = last escapedLines;
in "''" + introSpace + concatStringsSep introSpace (init escapedLines)
+ (if lastLine == "" then outroSpace else introSpace + lastLine) + "''";
in
if multiline && length lines > 1 then multilineResult else singlelineResult
@ -342,11 +370,11 @@ rec {
else if isList v then
if v == [] then "[ ]"
else "[" + introSpace
+ libStr.concatMapStringsSep introSpace (go (indent + " ")) v
+ concatMapStringsSep introSpace (go (indent + " ")) v
+ outroSpace + "]"
else if isFunction v then
let fna = lib.functionArgs v;
showFnas = concatStringsSep ", " (libAttr.mapAttrsToList
let fna = functionArgs v;
showFnas = concatStringsSep ", " (mapAttrsToList
(name: hasDefVal: if hasDefVal then name + "?" else name)
fna);
in if fna == {} then "<function>"
@ -359,10 +387,10 @@ rec {
else if v ? type && v.type == "derivation" then
"<derivation ${v.name or "???"}>"
else "{" + introSpace
+ libStr.concatStringsSep introSpace (libAttr.mapAttrsToList
+ concatStringsSep introSpace (mapAttrsToList
(name: value:
"${libStr.escapeNixIdentifier name} = ${
builtins.addErrorContext "while evaluating an attribute `${name}`"
"${escapeNixIdentifier name} = ${
addErrorContext "while evaluating an attribute `${name}`"
(go (indent + " ") value)
};") v)
+ outroSpace + "}"
@ -371,9 +399,7 @@ rec {
# PLIST handling
toPlist = {}: v: let
isFloat = builtins.isFloat or (x: false);
isPath = x: builtins.typeOf x == "path";
expr = ind: x: with builtins;
expr = ind: x:
if x == null then "" else
if isBool x then bool ind x else
if isInt x then int ind x else
@ -394,23 +420,23 @@ rec {
indent = ind: expr "\t${ind}";
item = ind: libStr.concatMapStringsSep "\n" (indent ind);
item = ind: concatMapStringsSep "\n" (indent ind);
list = ind: x: libStr.concatStringsSep "\n" [
list = ind: x: concatStringsSep "\n" [
(literal ind "<array>")
(item ind x)
(literal ind "</array>")
];
attrs = ind: x: libStr.concatStringsSep "\n" [
attrs = ind: x: concatStringsSep "\n" [
(literal ind "<dict>")
(attr ind x)
(literal ind "</dict>")
];
attr = let attrFilter = name: value: name != "_module" && value != null;
in ind: x: libStr.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList
(name: value: lib.optionals (attrFilter name value) [
in ind: x: concatStringsSep "\n" (flatten (mapAttrsToList
(name: value: optionals (attrFilter name value) [
(key "\t${ind}" name)
(expr "\t${ind}" value)
]) x));
@ -426,11 +452,10 @@ ${expr "" v}
* the Natural type.
*/
toDhall = { }@args: v:
with builtins;
let concatItems = lib.strings.concatStringsSep ", ";
let concatItems = concatStringsSep ", ";
in if isAttrs v then
"{ ${
concatItems (lib.attrsets.mapAttrsToList
concatItems (mapAttrsToList
(key: value: "${key} = ${toDhall args value}") v)
} }"
else if isList v then
@ -444,7 +469,7 @@ ${expr "" v}
else if v == null then
abort "generators.toDhall: cannot convert a null to Dhall"
else
builtins.toJSON v;
toJSON v;
/*
Translate a simple Nix expression to Lua representation with occasional
@ -488,7 +513,6 @@ ${expr "" v}
/* Interpret as variable bindings */
asBindings ? false,
}@args: v:
with builtins;
let
innerIndent = "${indent} ";
introSpace = if multiline then "\n${innerIndent}" else " ";
@ -501,9 +525,9 @@ ${expr "" v}
isLuaInline = { _type ? null, ... }: _type == "lua-inline";
generatedBindings =
assert lib.assertMsg (badVarNames == []) "Bad Lua var names: ${toPretty {} badVarNames}";
libStr.concatStrings (
lib.attrsets.mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v
assert assertMsg (badVarNames == []) "Bad Lua var names: ${toPretty {} badVarNames}";
concatStrings (
mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v
);
# https://en.wikibooks.org/wiki/Lua_Programming/variable#Variable_names
@ -515,7 +539,7 @@ ${expr "" v}
else if v == null then
"nil"
else if isInt v || isFloat v || isString v || isBool v then
builtins.toJSON v
toJSON v
else if isList v then
(if v == [ ] then "{}" else
"{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}")
@ -525,11 +549,11 @@ ${expr "" v}
"(${v.expr})"
else if v == { } then
"{}"
else if libAttr.isDerivation v then
else if isDerivation v then
''"${toString v}"''
else
"{${introSpace}${concatItems (
lib.attrsets.mapAttrsToList (key: value: "[${builtins.toJSON key}] = ${toLua innerArgs value}") v
mapAttrsToList (key: value: "[${toJSON key}] = ${toLua innerArgs value}") v
)}${outroSpace}}"
)
else
@ -542,4 +566,37 @@ ${expr "" v}
mkLuaInline :: String -> AttrSet
*/
mkLuaInline = expr: { _type = "lua-inline"; inherit expr; };
in
# Everything in this attrset is the public interface of the file.
{
inherit
mkDconfKeyValue
mkKeyValueDefault
mkLuaInline
mkValueStringDefault
toDconfINI
toDhall
toGitINI
toINI
toINIWithGlobalSection
toKeyValue
toLua
toPlist
toPretty
withRecursion
;
/* Generates JSON from an arbitrary (non-function) value.
* For more information see the documentation of the builtin.
*/
toJSON = {}: toJSON;
/* YAML has been a strict superset of JSON since 1.2, so we
* use toJSON. Before it only had a few differences referring
* to implicit typing rules, so it should work with older
* parsers as well.
*/
toYAML = {}: toJSON;
}