From 8b2d86b982c5d5c6f4c7402546d32b17eb6b07f8 Mon Sep 17 00:00:00 2001 From: benaryorg Date: Thu, 25 Jan 2024 22:09:23 +0000 Subject: pkgs.formats: toINIWithGlobalSection wrapper The new format is based on the existing wrapper and generates an INI file with an unnamed global section at the top as is used by *stunnel* for instance. Technically the INI format is a subset of this however testing, type checking, and API guarantees profit from two separate generators. Co-authored-by: tim-tx Signed-off-by: benaryorg --- .../manual/development/settings-options.section.md | 28 +++++ pkgs/pkgs-lib/formats.nix | 121 +++++++++++++-------- pkgs/pkgs-lib/tests/formats.nix | 95 ++++++++++++++++ 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 3a4800742b04..71ec9bbc8892 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 c78bd82e01ef..1b72270b43ca 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 72994803d584..3243f4058e68 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 = { -- cgit 1.4.1