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 <tim-tx@users.noreply.github.com>
Signed-off-by: benaryorg <binary@benary.org>
This commit is contained in:
benaryorg 2024-01-25 22:09:23 +00:00
parent 3e72e7c014
commit 8b2d86b982
No known key found for this signature in database
GPG Key ID: E2F22C5EDF20119D
3 changed files with 199 additions and 45 deletions

View File

@ -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` { }

View File

@ -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 [

View File

@ -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 = {