{ lib, pkgs }: let inherit (lib) boolToString concatStringsSep escape filterAttrs flatten hasPrefix id isAttrs isBool isDerivation isFloat isInt isList isString mapAttrs mapAttrsToList mkOption optionalAttrs optionalString pipe singleton strings toJSON toPretty types versionAtLeast warn ; inherit (lib.generators) mkValueStringDefault toGitINI toINI toINIWithGlobalSection toKeyValue toLua mkLuaInline toPlist ; inherit (lib.types) serializableValueWith attrsOf atom bool coercedTo either float int listOf luaInline mkOptionType nonEmptyListOf nullOr oneOf path str submodule ; # Attributes added accidentally in https://github.com/NixOS/nixpkgs/pull/335232 (2024-08-18) # Deprecated in https://github.com/NixOS/nixpkgs/pull/415666 (2025-06) allowAliases = pkgs.config.allowAliases or false; aliasWarning = name: warn "`formats.${name}` is deprecated; use `lib.types.${name}` instead."; aliases = mapAttrs aliasWarning { inherit attrsOf bool coercedTo either float int listOf luaInline mkOptionType nonEmptyListOf nullOr oneOf path str ; }; json2x = pkgs.buildPackages.callPackage ./formats/json2x/package.nix { }; in optionalAttrs allowAliases aliases // rec { /* Every following entry represents a format for program configuration files used for `settings`-style options (see https://github.com/NixOS/rfcs/pull/42). Each entry should look as follows: = : { # ^^ Parameters for controlling the format # The module system type most suitable for representing such a format # The description needs to be overwritten for recursive types type = ...; # Utility functions for convenience, or special interactions with the # format (optional) lib = { exampleFunction = ... # Types specific to the format (optional) types = { ... }; ... }; # generate :: Name -> Value -> Path # A function for generating a file with a value of such a type generate = ...; }); Please note that `pkgs` may not always be available for use due to the split options doc build introduced in fc614c37c653, so lazy evaluation of only the 'type' field is required. */ inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; }) javaProperties ; libconfig = (import ./formats/libconfig/default.nix { inherit lib pkgs; }).format; hocon = (import ./formats/hocon/default.nix { inherit lib pkgs; }).format; php = (import ./formats/php/default.nix { inherit lib pkgs; }).format; configobj = (import ./formats/configobj/default.nix { inherit lib pkgs; }).format; json = { }: { type = types.json; generate = name: value: pkgs.callPackage ( { runCommand, jq }: runCommand name { nativeBuildInputs = [ jq ]; inherit value; preferLocalBuild = true; __structuredAttrs = true; } # NIX_ATTRS_JSON_FILE won't have `value` if it's null, but jq returns null for missing properties anyway # jsonNull test keeps this in check '' jq .value "$NIX_ATTRS_JSON_FILE" > $out '' ) { }; }; yaml = yaml_1_1; yaml_1_1 = { }: { generate = name: value: pkgs.callPackage ( { runCommand, remarshal }: runCommand name { nativeBuildInputs = [ remarshal ]; inherit value; preferLocalBuild = true; __structuredAttrs = true; } '' remarshal --from json --to yaml-1.1 ${ # attributes with null values are omitted from the JSON with structured attrs # yaml_1_1Null test keeps this in check if value == null then ''<(echo "null")'' else ''--unwrap value "$NIX_ATTRS_JSON_FILE"'' } "$out" '' ) { }; type = serializableValueWith { typeName = "YAML 1.1"; }; }; yaml_1_2 = { }: { generate = name: value: pkgs.callPackage ( { runCommand, remarshal }: runCommand name { nativeBuildInputs = [ remarshal ]; inherit value; preferLocalBuild = true; __structuredAttrs = true; } '' json2yaml ${ # attributes with null values are omitted from the JSON with structured attrs # yaml_1_2Null test keeps this in check if value == null then ''<(echo "null")'' else ''--unwrap value "$NIX_ATTRS_JSON_FILE"'' } "$out" '' ) { }; type = serializableValueWith { typeName = "YAML 1.2"; }; }; # the ini formats share a lot of code inherit ( let singleIniAtom = nullOr (oneOf [ bool int float str ]) // { description = "INI atom (null, bool, int, float or string)"; }; iniAtom = { listsAsDuplicateKeys, listToValue, atomsCoercedToLists, }: let singleIniAtomOr = if atomsCoercedToLists then coercedTo singleIniAtom singleton else either singleIniAtom; in if listsAsDuplicateKeys then singleIniAtomOr (listOf singleIniAtom) // { description = singleIniAtom.description + " or a list of them for duplicate keys"; } else if listToValue != null then singleIniAtomOr (nonEmptyListOf singleIniAtom) // { description = singleIniAtom.description + " or a non-empty list of them"; } else singleIniAtom; iniSection = atom: attrsOf atom // { description = "section of an INI file (attrs of " + atom.description + ")"; }; maybeCoerceList = listToValue: if listToValue != null then mapAttrs (key: val: if isList val then listToValue val else val) else id; maybeCoerceAllLists = listToValue: if listToValue != null then mapAttrs (_: mapAttrs (key: val: if isList val then listToValue val else val)) else id; ignoredArgs = [ "listToValue" "atomsCoercedToLists" ]; in { ini = { # Represents lists as duplicate keys listsAsDuplicateKeys ? false, # Alternative to listsAsDuplicateKeys, converts list to non-list # listToValue :: [IniAtom] -> IniAtom listToValue ? null, # Merge multiple instances of the same key into a list atomsCoercedToLists ? null, ... }@args: assert listsAsDuplicateKeys -> listToValue == null; assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null); let atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists; atom = iniAtom { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; }; in { type = attrsOf (iniSection atom); lib.types.atom = atom; generate = name: value: pipe value [ (maybeCoerceAllLists listToValue) (toINI (removeAttrs args ignoredArgs)) (pkgs.writeText name) ]; }; iniWithGlobalSection = { # Represents lists as duplicate keys listsAsDuplicateKeys ? false, # Alternative to listsAsDuplicateKeys, converts list to non-list # listToValue :: [IniAtom] -> IniAtom listToValue ? null, # Merge multiple instances of the same key into a list atomsCoercedToLists ? null, ... }@args: assert listsAsDuplicateKeys -> listToValue == null; assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null); let atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists; atom = iniAtom { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; }; in { type = submodule { options = { sections = mkOption rec { type = attrsOf (iniSection atom); default = { }; description = type.description; }; globalSection = mkOption rec { type = iniSection atom; default = { }; description = "global " + type.description; }; }; }; lib.types.atom = atom; generate = name: { sections ? { }, globalSection ? { }, ... }: pkgs.writeText name ( toINIWithGlobalSection (removeAttrs args ignoredArgs) { globalSection = maybeCoerceList listToValue globalSection; sections = maybeCoerceAllLists listToValue sections; } ); }; gitIni = { listsAsDuplicateKeys ? false, ... }: let atom = iniAtom { inherit listsAsDuplicateKeys; listToValue = null; atomsCoercedToLists = false; }; in { type = attrsOf (attrsOf (either atom (attrsOf atom))); lib.types.atom = atom; generate = name: value: pkgs.writeText name (toGitINI value); }; } ) ini iniWithGlobalSection gitIni ; # As defined by systemd.syntax(7) # # null does not set any value, which allows for RFC42 modules to specify # optional config options. systemd = let mkValueString = mkValueStringDefault { }; mkKeyValue = k: v: if v == null then "# ${k} is unset" else "${k} = ${mkValueString v}"; rawFormat = ini { listsAsDuplicateKeys = true; inherit mkKeyValue; }; in rawFormat // { generate = name: value: warn "Direct use of `pkgs.formats.systemd` has been deprecated, please use `pkgs.formats.systemd { }` instead." rawFormat.generate name value; __functor = self: { }: rawFormat; }; keyValue = { # Represents lists as duplicate keys listsAsDuplicateKeys ? false, # Alternative to listsAsDuplicateKeys, converts list to non-list # listToValue :: [Atom] -> Atom listToValue ? null, ... }@args: assert listsAsDuplicateKeys -> listToValue == null; { type = let singleAtom = nullOr (oneOf [ bool int float str ]) // { description = "atom (null, bool, int, float or string)"; }; atom = if listsAsDuplicateKeys then coercedTo singleAtom singleton (listOf singleAtom) // { description = singleAtom.description + " or a list of them for duplicate keys"; } else if listToValue != null then coercedTo singleAtom singleton (nonEmptyListOf singleAtom) // { description = singleAtom.description + " or a non-empty list of them"; } else singleAtom; in attrsOf atom; generate = let transformValue = if listToValue != null then mapAttrs (key: val: if isList val then listToValue val else val) else id; finalArgs = removeAttrs args [ "listToValue" ]; in name: value: pkgs.writeText name (toKeyValue finalArgs (transformValue value)); }; toml = { }: { type = types.toml; generate = name: value: pkgs.callPackage ( { runCommand }: runCommand name { nativeBuildInputs = [ json2x ]; inherit value; preferLocalBuild = true; __structuredAttrs = true; } '' json2x toml --unwrap value "$NIX_ATTRS_JSON_FILE" "$out" '' ) { }; }; /* dzikoysk's CDN format, see https://github.com/dzikoysk/cdn The result is almost identical to YAML when there are no nested properties, but differs enough in the other case to warrant a separate format. (see https://github.com/dzikoysk/cdn#supported-formats) Currently used by Panda, Reposilite, and FunnyGuilds (as per the repo's readme). */ cdn = { }: { type = serializableValueWith { typeName = "CDN"; }; generate = name: value: pkgs.callPackage ( { runCommand, json2cdn }: runCommand name { nativeBuildInputs = [ json2cdn ]; value = toJSON value; preferLocalBuild = true; __structuredAttrs = true; } '' valuePath="$TMPDIR/value" printf "%s" "$value" > "$valuePath" json2cdn "$valuePath" > $out '' ) { }; }; /* For configurations of Elixir project, like config.exs or runtime.exs Most Elixir project are configured using the [Config] Elixir DSL Since Elixir has more types than Nix, we need a way to map Nix types to more than 1 Elixir type. To that end, this format provides its own library, and its own set of types. To be more detailed, a Nix attribute set could correspond in Elixir to a [Keyword list] (the more common type), or it could correspond to a [Map]. A Nix string could correspond in Elixir to a [String] (also called "binary"), an [Atom], or a list of chars (usually discouraged). A Nix array could correspond in Elixir to a [List] or a [Tuple]. Some more types exists, like records, regexes, but since they are less used, we can leave the `mkRaw` function as an escape hatch. For more information on how to use this format in modules, please refer to the Elixir section of the Nixos documentation. TODO: special Elixir values doesn't show up nicely in the documentation [Config]: [Keyword list]: [Map]: [String]: [Atom]: [List]: [Tuple]: */ elixirConf = let toElixir = value: if value == null then "nil" else if value == true then "true" else if value == false then "false" else if isInt value || isFloat value then toString value else if isString value then string value else if isAttrs value then attrs value else if isList value then list value else abort "formats.elixirConf: should never happen (value = ${value})"; escapeElixir = escape [ "\\" "#" "\"" ]; string = value: "\"${escapeElixir value}\""; attrs = set: if set ? _elixirType then specialType set else let toKeyword = name: value: "${name}: ${toElixir value}"; keywordList = concatStringsSep ", " (mapAttrsToList toKeyword set); in "[" + keywordList + "]"; listContent = values: concatStringsSep ", " (map toElixir values); list = values: "[" + (listContent values) + "]"; specialType = { value, _elixirType }: if _elixirType == "raw" then value else if _elixirType == "atom" then value else if _elixirType == "map" then elixirMap value else if _elixirType == "tuple" then tuple value else if _elixirType == "charlist" then charlist value else abort "formats.elixirConf: should never happen (_elixirType = ${_elixirType})"; elixirMap = set: let toEntry = name: value: "${toElixir name} => ${toElixir value}"; entries = concatStringsSep ", " (mapAttrsToList toEntry set); in "%{${entries}}"; tuple = values: "{${listContent values}}"; charlist = value: "~c\"${value}\""; toConf = let keyConfig = rootKey: key: value: "config ${rootKey}, ${key}, ${toElixir value}"; keyConfigs = rootKey: values: mapAttrsToList (keyConfig rootKey) values; in values: let rootConfigs = flatten (mapAttrsToList keyConfigs values); in '' import Config ${concatStringsSep "\n" rootConfigs} ''; in { elixir ? pkgs.elixir, }: { type = let valueType = nullOr (oneOf [ bool int float str (attrsOf valueType) (listOf valueType) ]) // { description = "Elixir value"; }; in attrsOf (attrsOf valueType); lib = let mkRaw = value: { inherit value; _elixirType = "raw"; }; in { inherit mkRaw; # Fetch an environment variable at runtime, with optional fallback mkGetEnv = { envVariable, fallback ? null, }: mkRaw "System.get_env(${toElixir envVariable}, ${toElixir fallback})"; /* Make an Elixir atom. Note: lowercase atoms still need to be prefixed by ':' */ mkAtom = value: { inherit value; _elixirType = "atom"; }; # Make an Elixir charlist out of a string. mkCharlist = value: { inherit value; _elixirType = "charlist"; }; # Make an Elixir tuple out of a list. mkTuple = value: { inherit value; _elixirType = "tuple"; }; # Make an Elixir map out of an attribute set. mkMap = value: { inherit value; _elixirType = "map"; }; /* Contains Elixir types. Every type it exports can also be replaced by raw Elixir code (i.e. every type is `either type rawElixir`). It also reexports standard types, wrapping them so that they can also be raw Elixir. */ types = let isElixirType = type: x: (x._elixirType or "") == type; rawElixir = mkOptionType { name = "rawElixir"; description = "raw elixir"; check = isElixirType "raw"; }; elixirOr = other: either other rawElixir; in { inherit rawElixir elixirOr; atom = elixirOr (mkOptionType { name = "elixirAtom"; description = "elixir atom"; check = isElixirType "atom"; }); charlist = elixirOr (mkOptionType { name = "elixirCharlist"; description = "elixir charlist"; check = isElixirType "charlist"; }); tuple = elixirOr (mkOptionType { name = "elixirTuple"; description = "elixir tuple"; check = isElixirType "tuple"; }); map = elixirOr (mkOptionType { name = "elixirMap"; description = "elixir map"; check = isElixirType "map"; }); # Wrap standard types, since anything in the Elixir configuration # can be raw Elixir } // mapAttrs (_name: type: elixirOr type) types; }; generate = name: value: pkgs.runCommand name { value = toConf value; nativeBuildInputs = [ elixir ]; preferLocalBuild = true; __structuredAttrs = true; } '' printf "%s" "$value" > "$out" mix format "$out" ''; }; lua = { asBindings ? false, multiline ? true, columnWidth ? 100, indentWidth ? 2, indentUsingTabs ? false, }: { type = let valueType = nullOr (oneOf [ bool float int path str luaInline (attrsOf valueType) (listOf valueType) ]) // { description = "lua value"; descriptionClass = "noun"; }; in if asBindings then attrsOf valueType else valueType; generate = name: value: pkgs.callPackage ( { runCommand, stylua }: runCommand name { nativeBuildInputs = [ stylua ]; inherit columnWidth; inherit indentWidth; indentType = if indentUsingTabs then "Tabs" else "Spaces"; value = toLua { inherit asBindings multiline; } value; preferLocalBuild = true; __structuredAttrs = true; } '' ${optionalString (!asBindings) '' echo -n 'return ' >> $out ''} printf "%s" "$value" >> $out stylua \ --no-editorconfig \ --line-endings Unix \ --column-width $columnWidth \ --indent-width $indentWidth \ --indent-type $indentType \ $out '' ) { }; # Alias for mkLuaInline lib.mkRaw = mkLuaInline; }; nixConf = { package, version, extraOptions ? "", checkAllErrors ? true, checkConfig ? true, }: let isNixAtLeast = versionAtLeast version; in assert isNixAtLeast "2.2"; { type = let atomType = nullOr (oneOf [ bool int float str path types.package ]); in attrsOf atomType; generate = let # note that list type has been omitted here as the separator varies, see `nix.settings.*` mkValueString = v: if v == null then "" else if isInt v then toString v else if isBool v then boolToString v else if isFloat v then strings.floatToString v else if isDerivation v then toString v else if builtins.isPath v then toString v else if isString v then v else if strings.isConvertibleWithToString v then toString v else abort "The nix conf value: ${toPretty { } v} can not be encoded"; mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}"; mkKeyValuePairs = attrs: concatStringsSep "\n" (mapAttrsToList mkKeyValue attrs); isExtra = key: hasPrefix "extra-" key; in name: value: pkgs.writeTextFile { inherit name; # workaround for https://github.com/NixOS/nix/issues/9487 # extra-* settings must come after their non-extra counterpart text = '' # WARNING: this file is generated from the nix.* options in # your NixOS configuration, typically # /etc/nixos/configuration.nix. Do not edit it! ${mkKeyValuePairs (filterAttrs (key: _: !(isExtra key)) value)} ${mkKeyValuePairs (filterAttrs (key: _: isExtra key) value)} ${extraOptions} ''; checkPhase = optionalString checkConfig ( if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then '' echo "Ignoring validation for cross-compilation" '' else let showCommand = if isNixAtLeast "2.20pre" then "config show" else "show-config"; in '' echo "Validating generated nix.conf" ln -s $out ./nix.conf set -e set +o pipefail NIX_CONF_DIR=$PWD \ ${package}/bin/nix ${showCommand} ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \ ${optionalString (isNixAtLeast "2.4pre") "--option experimental-features nix-command"} \ |& sed -e 's/^warning:/error:/' \ | (! grep '${if checkAllErrors then "^error:" else "^error: unknown setting"}') set -o pipefail '' ); }; }; # Outputs a succession of Python variable assignments # Useful for many Django-based services pythonVars = { }: { type = attrsOf (serializableValueWith { typeName = "Python"; }); lib = { mkRaw = value: { inherit value; _type = "raw"; }; }; generate = name: value: pkgs.callPackage ( { runCommand, python3, black, }: runCommand name { nativeBuildInputs = [ python3 black ]; imports = value._imports or [ ]; # value must be an attrset, type would verify that, # otherwise removeAttrs will fail. value = removeAttrs value [ "_imports" ]; pythonGen = pkgs.writeText "pythonGen" '' import ast import json import os def gen_ast_node(value: any) -> ast.expr: if type(value) is list: return ast.List(elts=[gen_ast_node(x) for x in value]) elif type(value) is dict: if value.get("_type") == "raw": return ast.parse(value["value"], mode="eval").body else: return ast.Dict( keys=[ast.Constant(k) for k in value], values=[gen_ast_node(v) for v in value.values()], ) else: return ast.Constant(value) tree = ast.Module(body=[], type_ignores=[]) with open(os.environ["NIX_ATTRS_JSON_FILE"], "r") as f: attrs = json.load(f) if attrs["imports"] is not None: for i in attrs["imports"]: tree.body.append(ast.parse(f"import {i}").body[0]) for key, val in attrs["value"].items(): tree.body.append(ast.Assign( targets=[ast.Name(id=key, ctx=ast.Store())], value=gen_ast_node(val), )) ast.fix_missing_locations(tree) print(ast.unparse(tree)) ''; preferLocalBuild = true; __structuredAttrs = true; } '' python3 "$pythonGen" > $out black $out '' ) { }; }; xml = { format ? "badgerfish", withHeader ? true, }: if format == "badgerfish" then { type = attrsOf (serializableValueWith { typeName = "XML"; }) // { description = "XML value"; }; generate = name: value: pkgs.callPackage ( { runCommand, libxml2Python, python3Packages, }: runCommand name { nativeBuildInputs = [ python3Packages.xmltodict libxml2Python ]; inherit value; pythonGen = pkgs.writeText "pythonGen" '' import json import os import xmltodict with open(os.environ["NIX_ATTRS_JSON_FILE"], "r") as f: value = json.load(f).get("value") assert type(value) is dict, "value must be an attrset" print(xmltodict.unparse(value, full_document=${ if withHeader then "True" else "False" }, pretty=True, indent=" " * 2)) ''; preferLocalBuild = true; __structuredAttrs = true; } '' python3 "$pythonGen" > $out xmllint $out > /dev/null '' ) { }; } else throw "pkgs.formats.xml: Unknown format: ${format}"; plist = { escape ? true, }: { type = let valueType = nullOr (oneOf [ bool int float str path (attrsOf valueType) (listOf valueType) ]) // { description = "Property list (plist) value"; }; in valueType; generate = name: value: pkgs.writeText name (toPlist { inherit escape; } value); }; hcl1 = let # Helper function to recursively transform values for HCL1 canonicalization # Rule: If an attribute value is an attribute set, wrap it in a list transform = value: if isAttrs value && !isDerivation value then # If it's an attribute set, transform it recursively and wrap in a list [ (mapAttrs (name: transform) value) ] else if isList value then # If it's already a list, transform each element map transform value else value; jsonFormat = json { }; in args: jsonFormat // { generate = name: value: jsonFormat.generate name (mapAttrs (_: transform) value); }; }