From d8d864311207e4f0965e4c3415b283366cd8005e Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 24 Sep 2024 15:25:03 +0200 Subject: [PATCH] lib.types: init attrsWith --- lib/options.nix | 13 ++++++++--- lib/types.nix | 60 ++++++++++++++++++++++++++----------------------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/lib/options.nix b/lib/options.nix index f4d0d9d36cfc930..f7de76c63866700 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -427,18 +427,25 @@ rec { Placeholders will not be quoted as they are not actual values: (showOption ["foo" "*" "bar"]) == "foo.*.bar" (showOption ["foo" "" "bar"]) == "foo..bar" + (showOption ["foo" "" "bar"]) == "foo..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 "\<(.*)\>"; + # "" # functionTo + # "" # attrsOf submoule + # "" # attrsWith { name = "customName"; elemType = submoule; } 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 = [ - "" # attrsOf (submodule {}) "*" # listOf (submodule {}) - "" # functionTo ]; - in if builtins.elem part specialIdentifiers + in if builtins.elem part specialIdentifiers || isNamedPlaceholder part != null then part else lib.strings.escapeNixIdentifier part; in (concatStringsSep ".") (map escapeOptionPart parts); diff --git a/lib/types.nix b/lib/types.nix index 6c4a66c4e3c0b5f..40d3ce4ec884be3 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -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 ++ [""]); - 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 = { + name ? "name", + elemType, + 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 ++ [""]); + getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["{${name}}"]); getSubModules = elemType.getSubModules; - substSubModules = m: lazyAttrsOf (elemType.substSubModules m); - functor = (defaultFunctor name) // { wrapped = elemType; }; + substSubModules = m: attrsWith { itemType = elemType.substSubModules m; }; + functor = (defaultFunctor typeName) // { wrapped = elemType; }; nestedTypes.elemType = elemType; };