{ dag, lib }: with lib; let isDagEntry = e: isAttrs e && (e ? data) && (e ? after) && (e ? before); dagContentType = elemType: types.submodule ({ name, ... }: { options = { data = mkOption { type = elemType; }; after = mkOption { type = with types; uniq (listOf str); }; before = mkOption { type = with types; uniq (listOf str); }; }; config = mkIf (elemType.name == "submodule") { data._module.args.dagName = name; }; }); in rec { # A directed acyclic graph of some inner type. # # Note, if the element type is a submodule then the `name` argument # will always be set to the string "data" since it picks up the # internal structure of the DAG values. To give access to the # "actual" attribute name a new submodule argument is provided with # the name `dagName`. dagOf = elemType: let convertAllToDags = let maybeConvert = n: v: if isDagEntry v then v else dag.entryAnywhere v; in map (def: def // { value = mapAttrs maybeConvert def.value; }); attrEquivalent = types.attrsOf (dagContentType elemType); in mkOptionType rec { name = "dagOf"; description = "DAG of ${elemType.description}s"; check = isAttrs; merge = loc: defs: attrEquivalent.merge loc (convertAllToDags defs); getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "" ]); getSubModules = elemType.getSubModules; substSubModules = m: dagOf (elemType.substSubModules m); functor = (defaultFunctor name) // { wrapped = elemType; }; }; # A directed acyclic graph of some inner type OR a list of that # inner type. This is a temporary hack for use by the # `programs.ssh.matchBlocks` and is only guaranteed to be vaguely # correct! # # In particular, adding a dependency on one of the "unnamed-N-M" # entries generated by a list value is almost guaranteed to destroy # the list's order. # # This function will be removed in version 20.09. listOrDagOf = elemType: let paddedIndexStr = list: i: let padWidth = stringLength (toString (length list)); in fixedWidthNumber padWidth i; convertAll = defs: let convertListValue = namePrefix: vs: let pad = paddedIndexStr vs; makeEntry = i: v: nameValuePair "${namePrefix}.${pad i}" v; in listToAttrs (imap1 makeEntry vs); convertValue = i: value: if isList value then convertListValue "unnamed-${paddedIndexStr defs i}" value else value; in imap1 (i: def: def // { value = convertValue i def.value; }) defs; dagType = dagOf elemType; in mkOptionType rec { name = "listOrDagOf"; description = "list or DAG of ${elemType.description}s"; check = x: isList x || dagType.check x; merge = loc: defs: dagType.merge loc (convertAll defs); getSubOptions = dagType.getSubOptions; getSubModules = dagType.getSubModules; substSubModules = m: listOrDagOf (elemType.substSubModules m); functor = (defaultFunctor name) // { wrapped = elemType; }; }; }