diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md
index 58a3d8448af5..f9bb6ff9cc41 100644
--- a/nixos/doc/manual/development/settings-options.section.md
+++ b/nixos/doc/manual/development/settings-options.section.md
@@ -66,6 +66,45 @@ have a predefined type and string generator already declared under
and returning a set with TOML-specific attributes `type` and
`generate` as specified [below](#pkgs-formats-result).
+`pkgs.formats.elixirConf { elixir ? pkgs.elixir }`
+
+: A function taking an attribute set with values
+
+ `elixir`
+
+ : The Elixir package which will be used to format the generated output
+
+ It returns a set with Elixir-Config-specific attributes `type`, `lib`, and
+ `generate` as specified [below](#pkgs-formats-result).
+
+ The `lib` attribute contains functions to be used in settings, for
+ generating special Elixir values:
+
+ `mkRaw elixirCode`
+
+ : Outputs the given string as raw Elixir code
+
+ `mkGetEnv { envVariable, fallback ? null }`
+
+ : Makes the configuration fetch an environment variable at runtime
+
+ `mkAtom atom`
+
+ : Outputs the given string as an Elixir atom, instead of the default
+ Elixir binary string. Note: lowercase atoms still needs to be prefixed
+ with `:`
+
+ `mkTuple array`
+
+ : Outputs the given array as an Elixir tuple, instead of the default
+ Elixir list
+
+ `mkMap attrset`
+
+ : Outputs the given attribute set as an Elixir map, instead of the
+ default Elixir keyword list
+
+
::: {#pkgs-formats-result}
These functions all return an attribute set with these values:
:::
@@ -74,6 +113,12 @@ These functions all return an attribute set with these values:
: A module system type representing a value of the format
+`lib`
+
+: Utility functions for convenience, or special interactions with the format.
+ This attribute is optional. It may contain inside a `types` attribute
+ containing types specific to this format.
+
`generate` *`filename jsonValue`*
: A function that can render a value of the format to a file. Returns
diff --git a/nixos/doc/manual/from_md/development/settings-options.section.xml b/nixos/doc/manual/from_md/development/settings-options.section.xml
index c9430b77579c..746011a2d075 100644
--- a/nixos/doc/manual/from_md/development/settings-options.section.xml
+++ b/nixos/doc/manual/from_md/development/settings-options.section.xml
@@ -137,6 +137,97 @@
+
+
+ pkgs.formats.elixirConf { elixir ? pkgs.elixir }
+
+
+
+ A function taking an attribute set with values
+
+
+
+
+ elixir
+
+
+
+ The Elixir package which will be used to format the
+ generated output
+
+
+
+
+
+ It returns a set with Elixir-Config-specific attributes
+ type, lib, and
+ generate as specified
+ below.
+
+
+ The lib attribute contains functions to
+ be used in settings, for generating special Elixir values:
+
+
+
+
+ mkRaw elixirCode
+
+
+
+ Outputs the given string as raw Elixir code
+
+
+
+
+
+ mkGetEnv { envVariable, fallback ? null }
+
+
+
+ Makes the configuration fetch an environment variable
+ at runtime
+
+
+
+
+
+ mkAtom atom
+
+
+
+ Outputs the given string as an Elixir atom, instead of
+ the default Elixir binary string. Note: lowercase
+ atoms still needs to be prefixed with
+ :
+
+
+
+
+
+ mkTuple array
+
+
+
+ Outputs the given array as an Elixir tuple, instead of
+ the default Elixir list
+
+
+
+
+
+ mkMap attrset
+
+
+
+ Outputs the given attribute set as an Elixir map,
+ instead of the default Elixir keyword list
+
+
+
+
+
+
These functions all return an attribute set with these values:
@@ -152,6 +243,19 @@
+
+
+ lib
+
+
+
+ Utility functions for convenience, or special interactions
+ with the format. This attribute is optional. It may contain
+ inside a types attribute containing types
+ specific to this format.
+
+
+ generate
diff --git a/pkgs/pkgs-lib/formats.nix b/pkgs/pkgs-lib/formats.nix
index 5e17519d4ce1..495a7094f9b4 100644
--- a/pkgs/pkgs-lib/formats.nix
+++ b/pkgs/pkgs-lib/formats.nix
@@ -14,6 +14,15 @@ rec {
# 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 = ...;
@@ -147,4 +156,202 @@ rec {
'';
};
+
+ /* 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 = { elixir ? pkgs.elixir }:
+ with lib; let
+ toElixir = value: with builtins;
+ 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
+ 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}}";
+
+ toConf = values:
+ let
+ keyConfig = rootKey: key: value:
+ "config ${rootKey}, ${key}, ${toElixir value}";
+ keyConfigs = rootKey: values: mapAttrsToList (keyConfig rootKey) values;
+ rootConfigs = flatten (mapAttrsToList keyConfigs values);
+ in
+ ''
+ import Config
+
+ ${concatStringsSep "\n" rootConfigs}
+ '';
+ in
+ {
+ type = with lib.types; 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 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 = with lib.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";
+ });
+
+ 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
+ } // lib.mapAttrs (_name: type: elixirOr type) lib.types;
+ };
+
+ generate = name: value: pkgs.runCommandNoCC name
+ {
+ value = toConf value;
+ passAsFile = [ "value" ];
+ nativeBuildInputs = [ elixir ];
+ } ''
+ cp "$valuePath" "$out"
+ mix format "$out"
+ '';
+ };
+
}