mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-14 06:00:33 +03:00
lib.types.attrsWith: add placeholder parameter
This commit is contained in:
parent
fd6bc1b590
commit
d504a1e680
7 changed files with 163 additions and 17 deletions
|
@ -420,20 +420,17 @@ rec {
|
||||||
Placeholders will not be quoted as they are not actual values:
|
Placeholders will not be quoted as they are not actual values:
|
||||||
(showOption ["foo" "*" "bar"]) == "foo.*.bar"
|
(showOption ["foo" "*" "bar"]) == "foo.*.bar"
|
||||||
(showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
|
(showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
|
||||||
|
(showOption ["foo" "<myPlaceholder>" "bar"]) == "foo.<myPlaceholder>.bar"
|
||||||
*/
|
*/
|
||||||
showOption = parts: let
|
showOption = parts: let
|
||||||
|
# If the part is a named placeholder of the form "<...>" don't escape it.
|
||||||
|
# It may cause misleading escaping if somebody uses literally "<...>" in their option names.
|
||||||
|
# This is the trade-off to allow for placeholders in option names.
|
||||||
|
isNamedPlaceholder = builtins.match "\<(.*)\>";
|
||||||
escapeOptionPart = part:
|
escapeOptionPart = part:
|
||||||
let
|
if part == "*" || isNamedPlaceholder part != null
|
||||||
# We assume that these are "special values" and not real configuration data.
|
then part
|
||||||
# If it is real configuration data, it is rendered incorrectly.
|
else lib.strings.escapeNixIdentifier part;
|
||||||
specialIdentifiers = [
|
|
||||||
"<name>" # attrsOf (submodule {})
|
|
||||||
"*" # listOf (submodule {})
|
|
||||||
"<function body>" # functionTo
|
|
||||||
];
|
|
||||||
in if builtins.elem part specialIdentifiers
|
|
||||||
then part
|
|
||||||
else lib.strings.escapeNixIdentifier part;
|
|
||||||
in (concatStringsSep ".") (map escapeOptionPart parts);
|
in (concatStringsSep ".") (map escapeOptionPart parts);
|
||||||
showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
|
showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
|
||||||
|
|
||||||
|
|
|
@ -1877,6 +1877,44 @@ runTests {
|
||||||
expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
|
expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
testAttrsWithName = {
|
||||||
|
expr = let
|
||||||
|
eval = evalModules {
|
||||||
|
modules = [
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
foo = lib.mkOption {
|
||||||
|
type = lib.types.attrsWith {
|
||||||
|
placeholder = "MyCustomPlaceholder";
|
||||||
|
elemType = lib.types.submodule {
|
||||||
|
options.bar = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 42;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
opt = eval.options.foo;
|
||||||
|
in
|
||||||
|
(opt.type.getSubOptions opt.loc).bar.loc;
|
||||||
|
expected = [
|
||||||
|
"foo"
|
||||||
|
"<MyCustomPlaceholder>"
|
||||||
|
"bar"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
testShowOptionWithPlaceholder = {
|
||||||
|
# <name>, *, should not be escaped. It is used as a placeholder by convention.
|
||||||
|
# Other symbols should be escaped. `{}`
|
||||||
|
expr = lib.showOption ["<name>" "<myName>" "*" "{foo}"];
|
||||||
|
expected = "<name>.<myName>.*.\"{foo}\"";
|
||||||
|
};
|
||||||
|
|
||||||
testCartesianProductOfEmptySet = {
|
testCartesianProductOfEmptySet = {
|
||||||
expr = cartesianProduct {};
|
expr = cartesianProduct {};
|
||||||
expected = [ {} ];
|
expected = [ {} ];
|
||||||
|
|
|
@ -391,6 +391,10 @@ checkConfigError 'The option `mergedLazyNonLazy'\'' in `.*'\'' is already declar
|
||||||
checkConfigOutput '^11$' config.lazyResult ./lazy-attrsWith.nix
|
checkConfigOutput '^11$' config.lazyResult ./lazy-attrsWith.nix
|
||||||
checkConfigError 'infinite recursion encountered' config.nonLazyResult ./lazy-attrsWith.nix
|
checkConfigError 'infinite recursion encountered' config.nonLazyResult ./lazy-attrsWith.nix
|
||||||
|
|
||||||
|
# AttrsWith placeholder tests
|
||||||
|
checkConfigOutput '^"mergedName.<id>.nested"$' config.result ./name-merge-attrsWith-1.nix
|
||||||
|
checkConfigError 'The option .mergedName. in .*\.nix. is already declared in .*\.nix' config.mergedName ./name-merge-attrsWith-2.nix
|
||||||
|
|
||||||
# Even with multiple assignments, a type error should be thrown if any of them aren't valid
|
# Even with multiple assignments, a type error should be thrown if any of them aren't valid
|
||||||
checkConfigError 'A definition for option .* is not of type .*' \
|
checkConfigError 'A definition for option .* is not of type .*' \
|
||||||
config.value ./declare-int-unsigned-value.nix ./define-value-list.nix ./define-value-int-positive.nix
|
config.value ./declare-int-unsigned-value.nix ./define-value-list.nix ./define-value-int-positive.nix
|
||||||
|
|
51
lib/tests/modules/name-merge-attrsWith-1.nix
Normal file
51
lib/tests/modules/name-merge-attrsWith-1.nix
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) types mkOption;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
# Module A
|
||||||
|
(
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
options.mergedName = mkOption {
|
||||||
|
default = { };
|
||||||
|
type = types.attrsWith {
|
||||||
|
placeholder = "id"; # <- This is beeing tested
|
||||||
|
elemType = types.submodule {
|
||||||
|
options.nested = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Module B
|
||||||
|
(
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
# defines the default placeholder "name"
|
||||||
|
# type merging should resolve to "id"
|
||||||
|
options.mergedName = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule { });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Output
|
||||||
|
(
|
||||||
|
{
|
||||||
|
options,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
options.result = mkOption {
|
||||||
|
default = lib.concatStringsSep "." (options.mergedName.type.getSubOptions options.mergedName.loc)
|
||||||
|
.nested.loc;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
38
lib/tests/modules/name-merge-attrsWith-2.nix
Normal file
38
lib/tests/modules/name-merge-attrsWith-2.nix
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) types mkOption;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
# Module A
|
||||||
|
(
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
options.mergedName = mkOption {
|
||||||
|
default = { };
|
||||||
|
type = types.attrsWith {
|
||||||
|
placeholder = "id"; # <- this is beeing tested
|
||||||
|
elemType = types.submodule {
|
||||||
|
options.nested = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Module B
|
||||||
|
(
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
options.mergedName = mkOption {
|
||||||
|
type = types.attrsWith {
|
||||||
|
placeholder = "other"; # <- define placeholder = "other" (conflict)
|
||||||
|
elemType = types.submodule { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
|
@ -608,17 +608,27 @@ rec {
|
||||||
lhs.lazy
|
lhs.lazy
|
||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
|
placeholder =
|
||||||
|
if lhs.placeholder == rhs.placeholder then
|
||||||
|
lhs.placeholder
|
||||||
|
else if lhs.placeholder == "name" then
|
||||||
|
rhs.placeholder
|
||||||
|
else if rhs.placeholder == "name" then
|
||||||
|
lhs.placeholder
|
||||||
|
else
|
||||||
|
null;
|
||||||
in
|
in
|
||||||
if elemType == null || lazy == null then
|
if elemType == null || lazy == null || placeholder == null then
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
inherit elemType lazy;
|
inherit elemType lazy placeholder;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
elemType,
|
elemType,
|
||||||
lazy ? false,
|
lazy ? false,
|
||||||
|
placeholder ? "name",
|
||||||
}:
|
}:
|
||||||
mkOptionType {
|
mkOptionType {
|
||||||
name = if lazy then "lazyAttrsOf" else "attrsOf";
|
name = if lazy then "lazyAttrsOf" else "attrsOf";
|
||||||
|
@ -645,16 +655,16 @@ rec {
|
||||||
(pushPositions defs)))
|
(pushPositions defs)))
|
||||||
);
|
);
|
||||||
emptyValue = { value = {}; };
|
emptyValue = { value = {}; };
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<${placeholder}>"]);
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit lazy; };
|
substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit lazy placeholder; };
|
||||||
functor = defaultFunctor "attrsWith" // {
|
functor = defaultFunctor "attrsWith" // {
|
||||||
wrappedDeprecationMessage = { loc }: lib.warn ''
|
wrappedDeprecationMessage = { loc }: lib.warn ''
|
||||||
The deprecated `type.functor.wrapped` attribute of the option `${showOption loc}` is accessed, use `type.nestedTypes.elemType` instead.
|
The deprecated `type.functor.wrapped` attribute of the option `${showOption loc}` is accessed, use `type.nestedTypes.elemType` instead.
|
||||||
'' elemType;
|
'' elemType;
|
||||||
payload = {
|
payload = {
|
||||||
# Important!: Add new function attributes here in case of future changes
|
# Important!: Add new function attributes here in case of future changes
|
||||||
inherit elemType lazy;
|
inherit elemType lazy placeholder;
|
||||||
};
|
};
|
||||||
inherit binOp;
|
inherit binOp;
|
||||||
};
|
};
|
||||||
|
|
|
@ -399,7 +399,7 @@ Composed types are types that take a type as parameter. `listOf
|
||||||
returned instead for the same `mkIf false` definition.
|
returned instead for the same `mkIf false` definition.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
`types.attrsWith` { *`elemType`*, *`lazy`* ? false }
|
`types.attrsWith` { *`elemType`*, *`lazy`* ? false, *`placeholder`* ? "name" }
|
||||||
|
|
||||||
: An attribute set of where all the values are of *`elemType`* type.
|
: An attribute set of where all the values are of *`elemType`* type.
|
||||||
|
|
||||||
|
@ -411,10 +411,18 @@ Composed types are types that take a type as parameter. `listOf
|
||||||
`lazy`
|
`lazy`
|
||||||
: Determines whether the attribute set is lazily evaluated. See: `types.lazyAttrsOf`
|
: Determines whether the attribute set is lazily evaluated. See: `types.lazyAttrsOf`
|
||||||
|
|
||||||
|
`placeholder` (`String`, default: `name` )
|
||||||
|
: Placeholder string in documentation for the attribute names.
|
||||||
|
The default value `name` results in the placeholder `<name>`
|
||||||
|
|
||||||
**Behavior**
|
**Behavior**
|
||||||
|
|
||||||
- `attrsWith { elemType = t; }` is equivalent to `attrsOf t`
|
- `attrsWith { elemType = t; }` is equivalent to `attrsOf t`
|
||||||
- `attrsWith { lazy = true; elemType = t; }` is equivalent to `lazyAttrsOf t`
|
- `attrsWith { lazy = true; elemType = t; }` is equivalent to `lazyAttrsOf t`
|
||||||
|
- `attrsWith { placeholder = "id"; elemType = t; }`
|
||||||
|
|
||||||
|
Displays the option as `foo.<id>` in the manual.
|
||||||
|
|
||||||
|
|
||||||
`types.uniq` *`t`*
|
`types.uniq` *`t`*
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue