Merge staging-next into staging
This commit is contained in:
+39
-20
@@ -203,40 +203,59 @@ rec {
|
||||
/* If this option is true, attrsets like { __pretty = fn; val = …; }
|
||||
will use fn to convert val to a pretty printed representation.
|
||||
(This means fn is type Val -> String.) */
|
||||
allowPrettyValues ? false
|
||||
}@args: v: with builtins;
|
||||
allowPrettyValues ? false,
|
||||
/* If this option is true, the output is indented with newlines for attribute sets and lists */
|
||||
multiline ? true
|
||||
}@args: let
|
||||
go = indent: v: with builtins;
|
||||
let isPath = v: typeOf v == "path";
|
||||
introSpace = if multiline then "\n${indent} " else " ";
|
||||
outroSpace = if multiline then "\n${indent}" else " ";
|
||||
in if isInt v then toString v
|
||||
else if isFloat v then "~${toString v}"
|
||||
else if isString v then ''"${libStr.escape [''"''] v}"''
|
||||
else if isString v then
|
||||
let
|
||||
# Separate a string into its lines
|
||||
newlineSplits = filter (v: ! isList v) (builtins.split "\n" v);
|
||||
# For a '' string terminated by a \n, which happens when the closing '' is on a new line
|
||||
multilineResult = "''" + introSpace + concatStringsSep introSpace (lib.init newlineSplits) + outroSpace + "''";
|
||||
# For a '' string not terminated by a \n, which happens when the closing '' is not on a new line
|
||||
multilineResult' = "''" + introSpace + concatStringsSep introSpace newlineSplits + "''";
|
||||
# For single lines, replace all newlines with their escaped representation
|
||||
singlelineResult = "\"" + libStr.escape [ "\"" ] (concatStringsSep "\\n" newlineSplits) + "\"";
|
||||
in if multiline && length newlineSplits > 1 then
|
||||
if lib.last newlineSplits == "" then multilineResult else multilineResult'
|
||||
else singlelineResult
|
||||
else if true == v then "true"
|
||||
else if false == v then "false"
|
||||
else if null == v then "null"
|
||||
else if isPath v then toString v
|
||||
else if isList v then "[ "
|
||||
+ libStr.concatMapStringsSep " " (toPretty args) v
|
||||
+ " ]"
|
||||
else if isList v then
|
||||
if v == [] then "[ ]"
|
||||
else "[" + introSpace
|
||||
+ libStr.concatMapStringsSep introSpace (go (indent + " ")) v
|
||||
+ outroSpace + "]"
|
||||
else if isFunction v then
|
||||
let fna = lib.functionArgs v;
|
||||
showFnas = concatStringsSep ", " (libAttr.mapAttrsToList
|
||||
(name: hasDefVal: if hasDefVal then name + "?" else name)
|
||||
fna);
|
||||
in if fna == {} then "<function>"
|
||||
else "<function, args: {${showFnas}}>"
|
||||
else if isAttrs v then
|
||||
# apply pretty values if allowed
|
||||
if attrNames v == [ "__pretty" "val" ] && allowPrettyValues
|
||||
then v.__pretty v.val
|
||||
# TODO: there is probably a better representation?
|
||||
else if v == {} then "{ }"
|
||||
else if v ? type && v.type == "derivation" then
|
||||
"<δ:${v.name}>"
|
||||
# "<δ:${concatStringsSep "," (builtins.attrNames v)}>"
|
||||
else "{ "
|
||||
+ libStr.concatStringsSep " " (libAttr.mapAttrsToList
|
||||
"<derivation ${v.drvPath}>"
|
||||
else "{" + introSpace
|
||||
+ libStr.concatStringsSep introSpace (libAttr.mapAttrsToList
|
||||
(name: value:
|
||||
"${toPretty args name} = ${toPretty args value};") v)
|
||||
+ " }"
|
||||
else if isFunction v then
|
||||
let fna = lib.functionArgs v;
|
||||
showFnas = concatStringsSep "," (libAttr.mapAttrsToList
|
||||
(name: hasDefVal: if hasDefVal then "(${name})" else name)
|
||||
fna);
|
||||
in if fna == {} then "<λ>"
|
||||
else "<λ:{${showFnas}}>"
|
||||
"${libStr.escapeNixIdentifier name} = ${go (indent + " ") value};") v)
|
||||
+ outroSpace + "}"
|
||||
else abort "generators.toPretty: should never happen (v = ${v})";
|
||||
in go "";
|
||||
|
||||
# PLIST handling
|
||||
toPlist = {}: v: let
|
||||
|
||||
@@ -107,6 +107,10 @@ rec {
|
||||
/* "Merge" option definitions by checking that they all have the same value. */
|
||||
mergeEqualOption = loc: defs:
|
||||
if defs == [] then abort "This case should never happen."
|
||||
# Return early if we only have one element
|
||||
# This also makes it work for functions, because the foldl' below would try
|
||||
# to compare the first element with itself, which is false for functions
|
||||
else if length defs == 1 then (elemAt defs 0).value
|
||||
else foldl' (val: def:
|
||||
if def.value != val then
|
||||
throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
|
||||
|
||||
+65
-7
@@ -445,32 +445,90 @@ runTests {
|
||||
expected = builtins.toJSON val;
|
||||
};
|
||||
|
||||
testToPretty = {
|
||||
expr = mapAttrs (const (generators.toPretty {})) rec {
|
||||
testToPretty =
|
||||
let
|
||||
deriv = derivation { name = "test"; builder = "/bin/sh"; system = builtins.currentSystem; };
|
||||
in {
|
||||
expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
|
||||
int = 42;
|
||||
float = 0.1337;
|
||||
bool = true;
|
||||
emptystring = "";
|
||||
string = ''fno"rd'';
|
||||
newlinestring = "\n";
|
||||
path = /. + "/foo";
|
||||
null_ = null;
|
||||
function = x: x;
|
||||
functionArgs = { arg ? 4, foo }: arg;
|
||||
list = [ 3 4 function [ false ] ];
|
||||
emptylist = [];
|
||||
attrs = { foo = null; "foo bar" = "baz"; };
|
||||
drv = derivation { name = "test"; system = builtins.currentSystem; };
|
||||
emptyattrs = {};
|
||||
drv = deriv;
|
||||
};
|
||||
expected = rec {
|
||||
int = "42";
|
||||
float = "~0.133700";
|
||||
bool = "true";
|
||||
emptystring = ''""'';
|
||||
string = ''"fno\"rd"'';
|
||||
newlinestring = "\"\\n\"";
|
||||
path = "/foo";
|
||||
null_ = "null";
|
||||
function = "<λ>";
|
||||
functionArgs = "<λ:{(arg),foo}>";
|
||||
function = "<function>";
|
||||
functionArgs = "<function, args: {arg?, foo}>";
|
||||
list = "[ 3 4 ${function} [ false ] ]";
|
||||
attrs = "{ \"foo\" = null; \"foo bar\" = \"baz\"; }";
|
||||
drv = "<δ:test>";
|
||||
emptylist = "[ ]";
|
||||
attrs = "{ foo = null; \"foo bar\" = \"baz\"; }";
|
||||
emptyattrs = "{ }";
|
||||
drv = "<derivation ${deriv.drvPath}>";
|
||||
};
|
||||
};
|
||||
|
||||
testToPrettyMultiline = {
|
||||
expr = mapAttrs (const (generators.toPretty { })) rec {
|
||||
list = [ 3 4 [ false ] ];
|
||||
attrs = { foo = null; bar.foo = "baz"; };
|
||||
newlinestring = "\n";
|
||||
multilinestring = ''
|
||||
hello
|
||||
there
|
||||
test
|
||||
'';
|
||||
multilinestring' = ''
|
||||
hello
|
||||
there
|
||||
test'';
|
||||
};
|
||||
expected = rec {
|
||||
list = ''
|
||||
[
|
||||
3
|
||||
4
|
||||
[
|
||||
false
|
||||
]
|
||||
]'';
|
||||
attrs = ''
|
||||
{
|
||||
bar = {
|
||||
foo = "baz";
|
||||
};
|
||||
foo = null;
|
||||
}'';
|
||||
newlinestring = "''\n \n''";
|
||||
multilinestring = ''
|
||||
'''
|
||||
hello
|
||||
there
|
||||
test
|
||||
''''';
|
||||
multilinestring' = ''
|
||||
'''
|
||||
hello
|
||||
there
|
||||
test''''';
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -233,6 +233,35 @@ checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf.
|
||||
checkConfigError 'The option .* is used but not defined' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix
|
||||
checkConfigOutput 24 config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix ./define-value-string.nix
|
||||
|
||||
## types.anything
|
||||
# Check that attribute sets are merged recursively
|
||||
checkConfigOutput null config.value.foo ./types-anything/nested-attrs.nix
|
||||
checkConfigOutput null config.value.l1.foo ./types-anything/nested-attrs.nix
|
||||
checkConfigOutput null config.value.l1.l2.foo ./types-anything/nested-attrs.nix
|
||||
checkConfigOutput null config.value.l1.l2.l3.foo ./types-anything/nested-attrs.nix
|
||||
# Attribute sets that are coercible to strings shouldn't be recursed into
|
||||
checkConfigOutput foo config.value.outPath ./types-anything/attrs-coercible.nix
|
||||
# Multiple lists aren't concatenated together
|
||||
checkConfigError 'The option .* has conflicting definitions' config.value ./types-anything/lists.nix
|
||||
# Check that all equalizable atoms can be used as long as all definitions are equal
|
||||
checkConfigOutput 0 config.value.int ./types-anything/equal-atoms.nix
|
||||
checkConfigOutput false config.value.bool ./types-anything/equal-atoms.nix
|
||||
checkConfigOutput '""' config.value.string ./types-anything/equal-atoms.nix
|
||||
checkConfigOutput / config.value.path ./types-anything/equal-atoms.nix
|
||||
checkConfigOutput null config.value.null ./types-anything/equal-atoms.nix
|
||||
checkConfigOutput 0.1 config.value.float ./types-anything/equal-atoms.nix
|
||||
# Functions can't be merged together
|
||||
checkConfigError "The option .* has conflicting definitions" config.value.multiple-lambdas ./types-anything/functions.nix
|
||||
checkConfigOutput '<LAMBDA>' config.value.single-lambda ./types-anything/functions.nix
|
||||
# Check that all mk* modifiers are applied
|
||||
checkConfigError 'attribute .* not found' config.value.mkiffalse ./types-anything/mk-mods.nix
|
||||
checkConfigOutput '{ }' config.value.mkiftrue ./types-anything/mk-mods.nix
|
||||
checkConfigOutput 1 config.value.mkdefault ./types-anything/mk-mods.nix
|
||||
checkConfigOutput '{ }' config.value.mkmerge ./types-anything/mk-mods.nix
|
||||
checkConfigOutput true config.value.mkbefore ./types-anything/mk-mods.nix
|
||||
checkConfigOutput 1 config.value.nested.foo ./types-anything/mk-mods.nix
|
||||
checkConfigOutput baz config.value.nested.bar.baz ./types-anything/mk-mods.nix
|
||||
|
||||
cat <<EOF
|
||||
====== module tests ======
|
||||
$pass Pass
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{ lib, ... }: {
|
||||
|
||||
options.value = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
};
|
||||
|
||||
config.value = {
|
||||
outPath = "foo";
|
||||
err = throw "err";
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{ lib, ... }: {
|
||||
|
||||
options.value = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
value.int = 0;
|
||||
value.bool = false;
|
||||
value.string = "";
|
||||
value.path = /.;
|
||||
value.null = null;
|
||||
value.float = 0.1;
|
||||
}
|
||||
{
|
||||
value.int = 0;
|
||||
value.bool = false;
|
||||
value.string = "";
|
||||
value.path = /.;
|
||||
value.null = null;
|
||||
value.float = 0.1;
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{ lib, ... }: {
|
||||
|
||||
options.value = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
value.single-lambda = x: x;
|
||||
value.multiple-lambdas = x: x;
|
||||
}
|
||||
{
|
||||
value.multiple-lambdas = x: x;
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{ lib, ... }: {
|
||||
|
||||
options.value = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
value = [ null ];
|
||||
}
|
||||
{
|
||||
value = [ null ];
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{ lib, ... }: {
|
||||
|
||||
options.value = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
value.mkiffalse = lib.mkIf false {};
|
||||
}
|
||||
{
|
||||
value.mkiftrue = lib.mkIf true {};
|
||||
}
|
||||
{
|
||||
value.mkdefault = lib.mkDefault 0;
|
||||
}
|
||||
{
|
||||
value.mkdefault = 1;
|
||||
}
|
||||
{
|
||||
value.mkmerge = lib.mkMerge [
|
||||
{}
|
||||
];
|
||||
}
|
||||
{
|
||||
value.mkbefore = lib.mkBefore true;
|
||||
}
|
||||
{
|
||||
value.nested = lib.mkMerge [
|
||||
{
|
||||
foo = lib.mkDefault 0;
|
||||
bar = lib.mkIf false 0;
|
||||
}
|
||||
(lib.mkIf true {
|
||||
foo = lib.mkIf true (lib.mkForce 1);
|
||||
bar = {
|
||||
baz = lib.mkDefault "baz";
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{ lib, ... }: {
|
||||
|
||||
options.value = lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
value.foo = null;
|
||||
}
|
||||
{
|
||||
value.l1.foo = null;
|
||||
}
|
||||
{
|
||||
value.l1.l2.foo = null;
|
||||
}
|
||||
{
|
||||
value.l1.l2.l3.foo = null;
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
@@ -104,6 +104,42 @@ rec {
|
||||
# When adding new types don't forget to document them in
|
||||
# nixos/doc/manual/development/option-types.xml!
|
||||
types = rec {
|
||||
|
||||
anything = mkOptionType {
|
||||
name = "anything";
|
||||
description = "anything";
|
||||
check = value: true;
|
||||
merge = loc: defs:
|
||||
let
|
||||
getType = value:
|
||||
if isAttrs value && isCoercibleToString value
|
||||
then "stringCoercibleSet"
|
||||
else builtins.typeOf value;
|
||||
|
||||
# Returns the common type of all definitions, throws an error if they
|
||||
# don't have the same type
|
||||
commonType = foldl' (type: def:
|
||||
if getType def.value == type
|
||||
then type
|
||||
else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
|
||||
) (getType (head defs).value) defs;
|
||||
|
||||
mergeFunction = {
|
||||
# Recursively merge attribute sets
|
||||
set = (attrsOf anything).merge;
|
||||
# Safe and deterministic behavior for lists is to only accept one definition
|
||||
# listOf only used to apply mkIf and co.
|
||||
list =
|
||||
if length defs > 1
|
||||
then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
|
||||
else (listOf anything).merge;
|
||||
# This is the type of packages, only accept a single definition
|
||||
stringCoercibleSet = mergeOneOption;
|
||||
# Otherwise fall back to only allowing all equal definitions
|
||||
}.${commonType} or mergeEqualOption;
|
||||
in mergeFunction loc defs;
|
||||
};
|
||||
|
||||
unspecified = mkOptionType {
|
||||
name = "unspecified";
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user