about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--nixos/doc/manual/development/settings-options.section.md28
-rw-r--r--pkgs/pkgs-lib/formats.nix121
-rw-r--r--pkgs/pkgs-lib/tests/formats.nix95
3 files changed, 199 insertions, 45 deletions
diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md
index 3a4800742b048..71ec9bbc88925 100644
--- a/nixos/doc/manual/development/settings-options.section.md
+++ b/nixos/doc/manual/development/settings-options.section.md
@@ -73,6 +73,34 @@ have a predefined type and string generator already declared under
 
     It returns a set with INI-specific attributes `type` and `generate`
     as specified [below](#pkgs-formats-result).
+    The type of the input is an *attrset* of sections; key-value pairs where
+    the key is the section name and the value is the corresponding content
+    which is also an *attrset* of key-value pairs for the actual key-value
+    mappings of the INI format.
+    The values of the INI atoms are subject to the above parameters (e.g. lists
+    may be transformed into multiple key-value pairs depending on
+    `listToValue`).
+
+`pkgs.formats.iniWithGlobalSection` { *`listsAsDuplicateKeys`* ? false, *`listToValue`* ? null, \.\.\. }
+
+:   A function taking an attribute set with values
+
+    `listsAsDuplicateKeys`
+
+    :   A boolean for controlling whether list values can be used to
+        represent duplicate INI keys
+
+    `listToValue`
+
+    :   A function for turning a list of values into a single value.
+
+    It returns a set with INI-specific attributes `type` and `generate`
+    as specified [below](#pkgs-formats-result).
+    The type of the input is an *attrset* of the structure
+    `{ sections = {}; globalSection = {}; }` where *sections* are several
+    sections as with *pkgs.formats.ini* and *globalSection* being just a single
+    attrset of key-value pairs for a single section, the global section which
+    preceedes the section definitions.
 
 `pkgs.formats.toml` { }
 
diff --git a/pkgs/pkgs-lib/formats.nix b/pkgs/pkgs-lib/formats.nix
index c78bd82e01ef1..1b72270b43ca4 100644
--- a/pkgs/pkgs-lib/formats.nix
+++ b/pkgs/pkgs-lib/formats.nix
@@ -95,29 +95,13 @@ rec {
 
   };
 
-  ini = {
-    # Represents lists as duplicate keys
-    listsAsDuplicateKeys ? false,
-    # Alternative to listsAsDuplicateKeys, converts list to non-list
-    # listToValue :: [IniAtom] -> IniAtom
-    listToValue ? null,
-    ...
-    }@args:
-    assert !listsAsDuplicateKeys || listToValue == null;
-    {
-
-    type = with lib.types; let
-
-      singleIniAtom = nullOr (oneOf [
-        bool
-        int
-        float
-        str
-      ]) // {
+  # the ini formats share a lot of code
+  inherit (
+    let
+      singleIniAtom = with lib.types; nullOr (oneOf [ bool int float str ]) // {
         description = "INI atom (null, bool, int, float or string)";
       };
-
-      iniAtom =
+      iniAtom = with lib.types; { listsAsDuplicateKeys, listToValue }:
         if listsAsDuplicateKeys then
           coercedTo singleIniAtom lib.singleton (listOf singleIniAtom) // {
             description = singleIniAtom.description + " or a list of them for duplicate keys";
@@ -128,21 +112,79 @@ rec {
           }
         else
           singleIniAtom;
+      iniSection = with lib.types; { listsAsDuplicateKeys, listToValue }@args:
+        attrsOf (iniAtom args) // {
+          description = "section of an INI file (attrs of " + (iniAtom args).description + ")";
+        };
 
-    in attrsOf (attrsOf iniAtom);
+      maybeToList = listToValue: if listToValue != null then lib.mapAttrs (key: val: if lib.isList val then listToValue val else val) else lib.id;
+    in {
+      ini = {
+        # Represents lists as duplicate keys
+        listsAsDuplicateKeys ? false,
+        # Alternative to listsAsDuplicateKeys, converts list to non-list
+        # listToValue :: [IniAtom] -> IniAtom
+        listToValue ? null,
+        ...
+        }@args:
+        assert listsAsDuplicateKeys -> listToValue == null;
+        {
 
-    generate = name: value:
-      let
-        transformedValue =
-          if listToValue != null
-          then
-            lib.mapAttrs (section: lib.mapAttrs (key: val:
-              if lib.isList val then listToValue val else val
-            )) value
-          else value;
-      in pkgs.writeText name (lib.generators.toINI (removeAttrs args ["listToValue"]) transformedValue);
+        type = lib.types.attrsOf (iniSection { listsAsDuplicateKeys = listsAsDuplicateKeys; listToValue = listToValue; });
 
-  };
+        generate = name: value:
+          lib.pipe value
+          [
+            (lib.mapAttrs (_: maybeToList listToValue))
+            (lib.generators.toINI (removeAttrs args ["listToValue"]))
+            (pkgs.writeText name)
+          ];
+      };
+
+      iniWithGlobalSection = {
+        # Represents lists as duplicate keys
+        listsAsDuplicateKeys ? false,
+        # Alternative to listsAsDuplicateKeys, converts list to non-list
+        # listToValue :: [IniAtom] -> IniAtom
+        listToValue ? null,
+        ...
+        }@args:
+        assert listsAsDuplicateKeys -> listToValue == null;
+        {
+          type = lib.types.submodule {
+            options = {
+              sections = lib.mkOption rec {
+                type = lib.types.attrsOf (iniSection { listsAsDuplicateKeys = listsAsDuplicateKeys; listToValue = listToValue; });
+                default = {};
+                description = type.description;
+              };
+              globalSection = lib.mkOption rec {
+                type = iniSection { listsAsDuplicateKeys = listsAsDuplicateKeys; listToValue = listToValue; };
+                default = {};
+                description = "global " + type.description;
+              };
+            };
+          };
+          generate = name: { sections ? {}, globalSection ? {}, ... }:
+            pkgs.writeText name (lib.generators.toINIWithGlobalSection (removeAttrs args ["listToValue"])
+            {
+              globalSection = maybeToList listToValue globalSection;
+              sections = lib.mapAttrs (_: maybeToList listToValue) sections;
+            });
+        };
+
+      gitIni = { listsAsDuplicateKeys ? false, ... }@args: {
+        type = let
+          atom = iniAtom {
+            listsAsDuplicateKeys = listsAsDuplicateKeys;
+            listToValue = null;
+          };
+        in with lib.types; attrsOf (attrsOf (either atom (attrsOf atom)));
+
+        generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
+      };
+
+    }) ini iniWithGlobalSection gitIni;
 
   # As defined by systemd.syntax(7)
   #
@@ -166,7 +208,7 @@ rec {
     listToValue ? null,
     ...
     }@args:
-    assert !listsAsDuplicateKeys || listToValue == null;
+    assert listsAsDuplicateKeys -> listToValue == null;
     {
 
     type = with lib.types; let
@@ -207,17 +249,6 @@ rec {
 
   };
 
-  gitIni = { listsAsDuplicateKeys ? false, ... }@args: {
-
-    type = with lib.types; let
-
-      iniAtom = (ini args).type/*attrsOf*/.functor.wrapped/*attrsOf*/.functor.wrapped;
-
-    in attrsOf (attrsOf (either iniAtom (attrsOf iniAtom)));
-
-    generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
-  };
-
   toml = {}: json {} // {
     type = with lib.types; let
       valueType = oneOf [
diff --git a/pkgs/pkgs-lib/tests/formats.nix b/pkgs/pkgs-lib/tests/formats.nix
index 72994803d5844..3243f4058e680 100644
--- a/pkgs/pkgs-lib/tests/formats.nix
+++ b/pkgs/pkgs-lib/tests/formats.nix
@@ -222,6 +222,101 @@ in runBuildTests {
     '';
   };
 
+  iniWithGlobalNoSections = shouldPass {
+    format = formats.iniWithGlobalSection {};
+    input = {};
+    expected = "";
+  };
+
+  iniWithGlobalOnlySections = shouldPass {
+    format = formats.iniWithGlobalSection {};
+    input = {
+      sections = {
+        foo = {
+          bar = "baz";
+        };
+      };
+    };
+    expected = ''
+      [foo]
+      bar=baz
+    '';
+  };
+
+  iniWithGlobalOnlyGlobal = shouldPass {
+    format = formats.iniWithGlobalSection {};
+    input = {
+      globalSection = {
+        bar = "baz";
+      };
+    };
+    expected = ''
+      bar=baz
+
+    '';
+  };
+
+  iniWithGlobalWrongSections = shouldFail {
+    format = formats.iniWithGlobalSection {};
+    input = {
+      foo = {};
+    };
+  };
+
+  iniWithGlobalEverything = shouldPass {
+    format = formats.iniWithGlobalSection {};
+    input = {
+      globalSection = {
+        bar = true;
+      };
+      sections = {
+        foo = {
+          bool = true;
+          int = 10;
+          float = 3.141;
+          str = "string";
+        };
+      };
+    };
+    expected = ''
+      bar=true
+
+      [foo]
+      bool=true
+      float=3.141000
+      int=10
+      str=string
+    '';
+  };
+
+  iniWithGlobalListToValue = shouldPass {
+    format = formats.iniWithGlobalSection { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
+    input = {
+      globalSection = {
+        bar = [ null true "test" 1.2 10 ];
+        baz = false;
+        qux = "qux";
+      };
+      sections = {
+        foo = {
+          bar = [ null true "test" 1.2 10 ];
+          baz = false;
+          qux = "qux";
+        };
+      };
+    };
+    expected = ''
+      bar=null, true, test, 1.200000, 10
+      baz=false
+      qux=qux
+
+      [foo]
+      bar=null, true, test, 1.200000, 10
+      baz=false
+      qux=qux
+    '';
+  };
+
   keyValueAtoms = shouldPass {
     format = formats.keyValue {};
     input = {