1cab362206
lib.formats.keyValue: init
421 lines
12 KiB
Nix
421 lines
12 KiB
Nix
{ lib, pkgs }:
|
|
rec {
|
|
|
|
/*
|
|
|
|
Every following entry represents a format for program configuration files
|
|
used for `settings`-style options (see https://github.com/NixOS/rfcs/pull/42).
|
|
Each entry should look as follows:
|
|
|
|
<format> = <parameters>: {
|
|
# ^^ Parameters for controlling the format
|
|
|
|
# The module system type most suitable for representing such a format
|
|
# 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 = ...;
|
|
|
|
});
|
|
*/
|
|
|
|
|
|
inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; })
|
|
javaProperties;
|
|
|
|
json = {}: {
|
|
|
|
type = with lib.types; let
|
|
valueType = nullOr (oneOf [
|
|
bool
|
|
int
|
|
float
|
|
str
|
|
path
|
|
(attrsOf valueType)
|
|
(listOf valueType)
|
|
]) // {
|
|
description = "JSON value";
|
|
};
|
|
in valueType;
|
|
|
|
generate = name: value: pkgs.callPackage ({ runCommand, jq }: runCommand name {
|
|
nativeBuildInputs = [ jq ];
|
|
value = builtins.toJSON value;
|
|
passAsFile = [ "value" ];
|
|
} ''
|
|
jq . "$valuePath"> $out
|
|
'') {};
|
|
|
|
};
|
|
|
|
yaml = {}: {
|
|
|
|
generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
|
|
nativeBuildInputs = [ remarshal ];
|
|
value = builtins.toJSON value;
|
|
passAsFile = [ "value" ];
|
|
} ''
|
|
json2yaml "$valuePath" "$out"
|
|
'') {};
|
|
|
|
type = with lib.types; let
|
|
valueType = nullOr (oneOf [
|
|
bool
|
|
int
|
|
float
|
|
str
|
|
path
|
|
(attrsOf valueType)
|
|
(listOf valueType)
|
|
]) // {
|
|
description = "YAML value";
|
|
};
|
|
in valueType;
|
|
|
|
};
|
|
|
|
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
|
|
]) // {
|
|
description = "INI atom (null, bool, int, float or string)";
|
|
};
|
|
|
|
iniAtom =
|
|
if listsAsDuplicateKeys then
|
|
coercedTo singleIniAtom lib.singleton (listOf singleIniAtom) // {
|
|
description = singleIniAtom.description + " or a list of them for duplicate keys";
|
|
}
|
|
else if listToValue != null then
|
|
coercedTo singleIniAtom lib.singleton (nonEmptyListOf singleIniAtom) // {
|
|
description = singleIniAtom.description + " or a non-empty list of them";
|
|
}
|
|
else
|
|
singleIniAtom;
|
|
|
|
in attrsOf (attrsOf iniAtom);
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
keyValue = {
|
|
# Represents lists as duplicate keys
|
|
listsAsDuplicateKeys ? false,
|
|
# Alternative to listsAsDuplicateKeys, converts list to non-list
|
|
# listToValue :: [Atom] -> Atom
|
|
listToValue ? null,
|
|
...
|
|
}@args:
|
|
assert !listsAsDuplicateKeys || listToValue == null;
|
|
{
|
|
|
|
type = with lib.types; let
|
|
|
|
singleAtom = nullOr (oneOf [
|
|
bool
|
|
int
|
|
float
|
|
str
|
|
]) // {
|
|
description = "atom (null, bool, int, float or string)";
|
|
};
|
|
|
|
atom =
|
|
if listsAsDuplicateKeys then
|
|
coercedTo singleAtom lib.singleton (listOf singleAtom) // {
|
|
description = singleAtom.description + " or a list of them for duplicate keys";
|
|
}
|
|
else if listToValue != null then
|
|
coercedTo singleAtom lib.singleton (nonEmptyListOf singleAtom) // {
|
|
description = singleAtom.description + " or a non-empty list of them";
|
|
}
|
|
else
|
|
singleAtom;
|
|
|
|
in attrsOf atom;
|
|
|
|
generate = name: value:
|
|
let
|
|
transformedValue =
|
|
if listToValue != null
|
|
then
|
|
lib.mapAttrs (key: val:
|
|
if lib.isList val then listToValue val else val
|
|
) value
|
|
else value;
|
|
in pkgs.writeText name (lib.generators.toKeyValue (removeAttrs args ["listToValue"]) transformedValue);
|
|
|
|
};
|
|
|
|
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 [
|
|
bool
|
|
int
|
|
float
|
|
str
|
|
path
|
|
(attrsOf valueType)
|
|
(listOf valueType)
|
|
] // {
|
|
description = "TOML value";
|
|
};
|
|
in valueType;
|
|
|
|
generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
|
|
nativeBuildInputs = [ remarshal ];
|
|
value = builtins.toJSON value;
|
|
passAsFile = [ "value" ];
|
|
} ''
|
|
json2toml "$valuePath" "$out"
|
|
'') {};
|
|
|
|
};
|
|
|
|
/* 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.runCommand name
|
|
{
|
|
value = toConf value;
|
|
passAsFile = [ "value" ];
|
|
nativeBuildInputs = [ elixir ];
|
|
} ''
|
|
cp "$valuePath" "$out"
|
|
mix format "$out"
|
|
'';
|
|
};
|
|
|
|
}
|