nixpkgs/pkgs/pkgs-lib/formats.nix
Silvan Mosberger 374e6bcc40 treewide: Format all Nix files
Format all Nix files using the officially approved formatter,
making the CI check introduced in the previous commit succeed:

  nix-build ci -A fmt.check

This is the next step of the of the [implementation](https://github.com/NixOS/nixfmt/issues/153)
of the accepted [RFC 166](https://github.com/NixOS/rfcs/pull/166).

This commit will lead to merge conflicts for a number of PRs,
up to an estimated ~1100 (~33%) among the PRs with activity in the past 2
months, but that should be lower than what it would be without the previous
[partial treewide format](https://github.com/NixOS/nixpkgs/pull/322537).

Merge conflicts caused by this commit can now automatically be resolved while rebasing using the
[auto-rebase script](8616af08d9/maintainers/scripts/auto-rebase).

If you run into any problems regarding any of this, please reach out to the
[formatting team](https://nixos.org/community/teams/formatting/) by
pinging @NixOS/nix-formatting.
2025-04-01 20:10:43 +02:00

898 lines
24 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 = ...;
});
Please note that `pkgs` may not always be available for use due to the split
options doc build introduced in fc614c37c653, so lazy evaluation of only the
'type' field is required.
*/
inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; })
javaProperties
;
libconfig = (import ./formats/libconfig/default.nix { inherit lib pkgs; }).format;
hocon = (import ./formats/hocon/default.nix { inherit lib pkgs; }).format;
php = (import ./formats/php/default.nix { inherit lib pkgs; }).format;
inherit (lib) mkOptionType;
inherit (lib.types)
nullOr
oneOf
coercedTo
listOf
nonEmptyListOf
attrsOf
either
;
inherit (lib.types)
bool
int
float
str
path
luaInline
;
json =
{ }:
{
type =
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" ];
preferLocalBuild = true;
}
''
jq . "$valuePath"> $out
''
) { };
};
yaml = yaml_1_1;
yaml_1_1 =
{ }:
{
generate =
name: value:
pkgs.callPackage (
{ runCommand, remarshal_0_17 }:
runCommand name
{
nativeBuildInputs = [ remarshal_0_17 ];
value = builtins.toJSON value;
passAsFile = [ "value" ];
preferLocalBuild = true;
}
''
json2yaml "$valuePath" "$out"
''
) { };
type =
let
valueType =
nullOr (oneOf [
bool
int
float
str
path
(attrsOf valueType)
(listOf valueType)
])
// {
description = "YAML value";
};
in
valueType;
};
# the ini formats share a lot of code
inherit
(
let
singleIniAtom =
nullOr (oneOf [
bool
int
float
str
])
// {
description = "INI atom (null, bool, int, float or string)";
};
iniAtom =
{
listsAsDuplicateKeys,
listToValue,
atomsCoercedToLists,
}:
let
singleIniAtomOr =
if atomsCoercedToLists then coercedTo singleIniAtom lib.singleton else either singleIniAtom;
in
if listsAsDuplicateKeys then
singleIniAtomOr (listOf singleIniAtom)
// {
description = singleIniAtom.description + " or a list of them for duplicate keys";
}
else if listToValue != null then
singleIniAtomOr (nonEmptyListOf singleIniAtom)
// {
description = singleIniAtom.description + " or a non-empty list of them";
}
else
singleIniAtom;
iniSection =
atom:
attrsOf atom
// {
description = "section of an INI file (attrs of " + atom.description + ")";
};
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,
# Merge multiple instances of the same key into a list
atomsCoercedToLists ? null,
...
}@args:
assert listsAsDuplicateKeys -> listToValue == null;
assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
let
atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
atom = iniAtom {
inherit listsAsDuplicateKeys listToValue;
atomsCoercedToLists = atomsCoercedToLists';
};
in
{
type = lib.types.attrsOf (iniSection atom);
lib.types.atom = atom;
generate =
name: value:
lib.pipe value [
(lib.mapAttrs (_: maybeToList listToValue))
(lib.generators.toINI (
removeAttrs args [
"listToValue"
"atomsCoercedToLists"
]
))
(pkgs.writeText name)
];
};
iniWithGlobalSection =
{
# Represents lists as duplicate keys
listsAsDuplicateKeys ? false,
# Alternative to listsAsDuplicateKeys, converts list to non-list
# listToValue :: [IniAtom] -> IniAtom
listToValue ? null,
# Merge multiple instances of the same key into a list
atomsCoercedToLists ? null,
...
}@args:
assert listsAsDuplicateKeys -> listToValue == null;
assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
let
atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
atom = iniAtom {
inherit listsAsDuplicateKeys listToValue;
atomsCoercedToLists = atomsCoercedToLists';
};
in
{
type = lib.types.submodule {
options = {
sections = lib.mkOption rec {
type = lib.types.attrsOf (iniSection atom);
default = { };
description = type.description;
};
globalSection = lib.mkOption rec {
type = iniSection atom;
default = { };
description = "global " + type.description;
};
};
};
lib.types.atom = atom;
generate =
name:
{
sections ? { },
globalSection ? { },
...
}:
pkgs.writeText name (
lib.generators.toINIWithGlobalSection
(removeAttrs args [
"listToValue"
"atomsCoercedToLists"
])
{
globalSection = maybeToList listToValue globalSection;
sections = lib.mapAttrs (_: maybeToList listToValue) sections;
}
);
};
gitIni =
{
listsAsDuplicateKeys ? false,
...
}@args:
let
atom = iniAtom {
inherit listsAsDuplicateKeys;
listToValue = null;
atomsCoercedToLists = false;
};
in
{
type = attrsOf (attrsOf (either atom (attrsOf atom)));
lib.types.atom = atom;
generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
};
}
)
ini
iniWithGlobalSection
gitIni
;
# As defined by systemd.syntax(7)
#
# null does not set any value, which allows for RFC42 modules to specify
# optional config options.
systemd =
let
mkValueString = lib.generators.mkValueStringDefault { };
mkKeyValue = k: v: if v == null then "# ${k} is unset" else "${k} = ${mkValueString v}";
in
ini {
listsAsDuplicateKeys = true;
inherit mkKeyValue;
};
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 =
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
);
};
toml =
{ }:
json { }
// {
type =
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" ];
preferLocalBuild = true;
}
''
json2toml "$valuePath" "$out"
''
) { };
};
/*
dzikoysk's CDN format, see https://github.com/dzikoysk/cdn
The result is almost identical to YAML when there are no nested properties,
but differs enough in the other case to warrant a separate format.
(see https://github.com/dzikoysk/cdn#supported-formats)
Currently used by Panda, Reposilite, and FunnyGuilds (as per the repo's readme).
*/
cdn =
{ }:
json { }
// {
type =
let
valueType =
nullOr (oneOf [
bool
int
float
str
path
(attrsOf valueType)
(listOf valueType)
])
// {
description = "CDN value";
};
in
valueType;
generate =
name: value:
pkgs.callPackage (
{ runCommand, json2cdn }:
runCommand name
{
nativeBuildInputs = [ json2cdn ];
value = builtins.toJSON value;
passAsFile = [ "value" ];
preferLocalBuild = true;
}
''
json2cdn "$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,
}:
let
toElixir =
value:
if value == null then
"nil"
else if value == true then
"true"
else if value == false then
"false"
else if lib.isInt value || lib.isFloat value then
toString value
else if lib.isString value then
string value
else if lib.isAttrs value then
attrs value
else if lib.isList value then
list value
else
abort "formats.elixirConf: should never happen (value = ${value})";
escapeElixir = lib.escape [
"\\"
"#"
"\""
];
string = value: "\"${escapeElixir value}\"";
attrs =
set:
if set ? _elixirType then
specialType set
else
let
toKeyword = name: value: "${name}: ${toElixir value}";
keywordList = lib.concatStringsSep ", " (lib.mapAttrsToList toKeyword set);
in
"[" + keywordList + "]";
listContent = values: lib.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 = lib.concatStringsSep ", " (lib.mapAttrsToList toEntry set);
in
"%{${entries}}";
tuple = values: "{${listContent values}}";
toConf =
values:
let
keyConfig =
rootKey: key: value:
"config ${rootKey}, ${key}, ${toElixir value}";
keyConfigs = rootKey: values: lib.mapAttrsToList (keyConfig rootKey) values;
rootConfigs = lib.flatten (lib.mapAttrsToList keyConfigs values);
in
''
import Config
${lib.concatStringsSep "\n" rootConfigs}
'';
in
{
type =
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 =
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 ];
preferLocalBuild = true;
}
''
cp "$valuePath" "$out"
mix format "$out"
'';
};
lua =
{
asBindings ? false,
multiline ? true,
columnWidth ? 100,
indentWidth ? 2,
indentUsingTabs ? false,
}:
{
type =
let
valueType =
nullOr (oneOf [
bool
float
int
path
str
luaInline
(attrsOf valueType)
(listOf valueType)
])
// {
description = "lua value";
descriptionClass = "noun";
};
in
if asBindings then attrsOf valueType else valueType;
generate =
name: value:
pkgs.callPackage (
{ runCommand, stylua }:
runCommand name
{
nativeBuildInputs = [ stylua ];
inherit columnWidth;
inherit indentWidth;
indentType = if indentUsingTabs then "Tabs" else "Spaces";
value = lib.generators.toLua { inherit asBindings multiline; } value;
passAsFile = [ "value" ];
preferLocalBuild = true;
}
''
${lib.optionalString (!asBindings) ''
echo -n 'return ' >> $out
''}
cat $valuePath >> $out
stylua \
--no-editorconfig \
--line-endings Unix \
--column-width $columnWidth \
--indent-width $indentWidth \
--indent-type $indentType \
$out
''
) { };
# Alias for mkLuaInline
lib.mkRaw = lib.mkLuaInline;
};
# Outputs a succession of Python variable assignments
# Useful for many Django-based services
pythonVars =
{ }:
{
type =
let
valueType =
nullOr (oneOf [
bool
float
int
path
str
(attrsOf valueType)
(listOf valueType)
])
// {
description = "Python value";
};
in
attrsOf valueType;
generate =
name: value:
pkgs.callPackage (
{
runCommand,
python3,
black,
}:
runCommand name
{
nativeBuildInputs = [
python3
black
];
value = builtins.toJSON value;
pythonGen = ''
import json
import os
with open(os.environ["valuePath"], "r") as f:
for key, value in json.load(f).items():
print(f"{key} = {repr(value)}")
'';
passAsFile = [
"value"
"pythonGen"
];
preferLocalBuild = true;
}
''
cat "$valuePath"
python3 "$pythonGenPath" > $out
black $out
''
) { };
};
xml =
{
format ? "badgerfish",
withHeader ? true,
}:
if format == "badgerfish" then
{
type =
let
valueType =
nullOr (oneOf [
bool
int
float
str
path
(attrsOf valueType)
(listOf valueType)
])
// {
description = "XML value";
};
in
valueType;
generate =
name: value:
pkgs.callPackage (
{
runCommand,
python3,
libxml2Python,
}:
runCommand name
{
nativeBuildInputs = [
python3
python3.pkgs.xmltodict
libxml2Python
];
value = builtins.toJSON value;
pythonGen = ''
import json
import os
import xmltodict
with open(os.environ["valuePath"], "r") as f:
print(xmltodict.unparse(json.load(f), full_document=${
if withHeader then "True" else "False"
}, pretty=True, indent=" " * 2))
'';
passAsFile = [
"value"
"pythonGen"
];
preferLocalBuild = true;
}
''
python3 "$pythonGenPath" > $out
xmllint $out > /dev/null
''
) { };
}
else
throw "pkgs.formats.xml: Unknown format: ${format}";
}