Skip to content

Commit

Permalink
lib.types: init attrsWith
Browse files Browse the repository at this point in the history
  • Loading branch information
hsjobeki committed Sep 27, 2024
1 parent dcfdb36 commit 6432843
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 39 deletions.
29 changes: 18 additions & 11 deletions lib/options.nix
Original file line number Diff line number Diff line change
Expand Up @@ -427,21 +427,28 @@ rec {
Placeholders will not be quoted as they are not actual values:
(showOption ["foo" "*" "bar"]) == "foo.*.bar"
(showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
(showOption ["foo" "<myPlaceholder>" "bar"]) == "foo.<myPlaceholder>.bar"
*/
showOption = parts: let
# If the part is a named placeholder of the form "<...>" don't escape it.
# Required for compatibility with: namedAttrsOf
# Can lead to misleading escaping if somebody uses literally "<...>" in their option names.
# This is the trade-off to allow for named placeholders in option names.
isNamedPlaceholder = builtins.match "\<(.*)\>";
# "<function body>" # functionTo
# "<name>" # attrsOf submoule
# "<customName>" # attrsWith { name = "customName"; elemType = submoule; }
# We assume that these are "special values" and not real configuration data.
# If it is real configuration data, it is rendered incorrectly.
specialIdentifiers = [
"*" # listOf (submodule {})
];
escapeOptionPart = part:
let
# We assume that these are "special values" and not real configuration data.
# If it is real configuration data, it is rendered incorrectly.
specialIdentifiers = [
"<name>" # attrsOf (submodule {})
"*" # listOf (submodule {})
"<function body>" # functionTo
];
in if builtins.elem part specialIdentifiers
then part
else lib.strings.escapeNixIdentifier part;
if builtins.elem part specialIdentifiers || isNamedPlaceholder part != null
then part
else lib.strings.escapeNixIdentifier part;
in (concatStringsSep ".") (map escapeOptionPart parts);

showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);

showDefs = defs: concatMapStrings (def:
Expand Down
60 changes: 32 additions & 28 deletions lib/types.nix
Original file line number Diff line number Diff line change
Expand Up @@ -568,48 +568,52 @@ rec {
substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
};

attrsOf = elemType: mkOptionType rec {
name = "attrsOf";
description = "attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
descriptionClass = "composite";
check = isAttrs;
merge = loc: defs:
mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
(mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
)
# Push down position info.
(map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
emptyValue = { value = {}; };
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: attrsOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
nestedTypes.elemType = elemType;
};
attrsOf = elemType: attrsWith { inherit elemType; };

# A version of attrsOf that's lazy in its values at the expense of
# conditional definitions not working properly. E.g. defining a value with
# `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
# attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
# error that it's not defined. Use only if conditional definitions don't make sense.
lazyAttrsOf = elemType: mkOptionType rec {
name = "lazyAttrsOf";
description = "lazy attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
descriptionClass = "composite";
check = isAttrs;
merge = loc: defs:
lazyAttrsOf = elemType: attrsWith { inherit elemType; lazy = true; };

# base type for lazyAttrsOf and attrsOf
attrsWith = {
elemType,
name ? "name",
lazy ? false,
}:
let
typeName = "attrsOf";
lazyMergeFn = loc: defs:
zipAttrsWith (name: defs:
let merged = mergeDefinitions (loc ++ [name]) elemType defs;
# mergedValue will trigger an appropriate error when accessed
in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
)
# Push down position info.
(map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
(pushPositions defs);

mergeFn = loc: defs:
mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
(mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
)
# Push down position info.
(pushPositions defs)));
# Push down position info.
pushPositions = map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value);
in
mkOptionType {
name = typeName;
description = (if lazy then "lazy attribute set" else "attribute set") + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
descriptionClass = "composite";
check = isAttrs;
merge = if lazy then lazyMergeFn else mergeFn;
emptyValue = { value = {}; };
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["{${name}}"]);
getSubModules = elemType.getSubModules;
substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit name lazy; };
functor = (defaultFunctor typeName) // { wrapped = elemType; };
nestedTypes.elemType = elemType;
};

Expand Down

0 comments on commit 6432843

Please sign in to comment.