summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--nixos/doc/manual/development/settings-options.section.md45
-rw-r--r--nixos/doc/manual/from_md/development/settings-options.section.xml104
-rw-r--r--pkgs/pkgs-lib/formats.nix207
3 files changed, 356 insertions, 0 deletions
diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md
index 58a3d8448af..f9bb6ff9cc4 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 c9430b77579..746011a2d07 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 @@
           </para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term>
+          <literal>pkgs.formats.elixirConf { elixir ? pkgs.elixir }</literal>
+        </term>
+        <listitem>
+          <para>
+            A function taking an attribute set with values
+          </para>
+          <variablelist>
+            <varlistentry>
+              <term>
+                <literal>elixir</literal>
+              </term>
+              <listitem>
+                <para>
+                  The Elixir package which will be used to format the
+                  generated output
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+          <para>
+            It returns a set with Elixir-Config-specific attributes
+            <literal>type</literal>, <literal>lib</literal>, and
+            <literal>generate</literal> as specified
+            <link linkend="pkgs-formats-result">below</link>.
+          </para>
+          <para>
+            The <literal>lib</literal> attribute contains functions to
+            be used in settings, for generating special Elixir values:
+          </para>
+          <variablelist>
+            <varlistentry>
+              <term>
+                <literal>mkRaw elixirCode</literal>
+              </term>
+              <listitem>
+                <para>
+                  Outputs the given string as raw Elixir code
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <literal>mkGetEnv { envVariable, fallback ? null }</literal>
+              </term>
+              <listitem>
+                <para>
+                  Makes the configuration fetch an environment variable
+                  at runtime
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <literal>mkAtom atom</literal>
+              </term>
+              <listitem>
+                <para>
+                  Outputs the given string as an Elixir atom, instead of
+                  the default Elixir binary string. Note: lowercase
+                  atoms still needs to be prefixed with
+                  <literal>:</literal>
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <literal>mkTuple array</literal>
+              </term>
+              <listitem>
+                <para>
+                  Outputs the given array as an Elixir tuple, instead of
+                  the default Elixir list
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <literal>mkMap attrset</literal>
+              </term>
+              <listitem>
+                <para>
+                  Outputs the given attribute set as an Elixir map,
+                  instead of the default Elixir keyword list
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        </listitem>
+      </varlistentry>
     </variablelist>
     <para xml:id="pkgs-formats-result">
       These functions all return an attribute set with these values:
@@ -154,6 +245,19 @@
       </varlistentry>
       <varlistentry>
         <term>
+          <literal>lib</literal>
+        </term>
+        <listitem>
+          <para>
+            Utility functions for convenience, or special interactions
+            with the format. This attribute is optional. It may contain
+            inside a <literal>types</literal> attribute containing types
+            specific to this format.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
           <literal>generate</literal>
           <emphasis><literal>filename jsonValue</literal></emphasis>
         </term>
diff --git a/pkgs/pkgs-lib/formats.nix b/pkgs/pkgs-lib/formats.nix
index 5e17519d4ce..495a7094f9b 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]: <https://hexdocs.pm/elixir/Config.html>
+    [Keyword list]: <https://hexdocs.pm/elixir/Keyword.html>
+    [Map]: <https://hexdocs.pm/elixir/Map.html>
+    [String]: <https://hexdocs.pm/elixir/String.html>
+    [Atom]: <https://hexdocs.pm/elixir/Atom.html>
+    [List]: <https://hexdocs.pm/elixir/List.html>
+    [Tuple]: <https://hexdocs.pm/elixir/Tuple.html>
+  */
+  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"
+      '';
+    };
+
 }