Skip to content

Commit

Permalink
pkgs-lib: Implement settings format for EDN
Browse files Browse the repository at this point in the history
  • Loading branch information
Sohalt committed Dec 16, 2023
1 parent 946c0ca commit c16892a
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 0 deletions.
42 changes: 42 additions & 0 deletions nixos/doc/manual/development/settings-options.section.md
Expand Up @@ -118,6 +118,48 @@ have a predefined type and string generator already declared under
: Outputs the given attribute set as an Elixir map, instead of the
default Elixir keyword list

`pkgs.formats.edn { keywordTransform ? (kw: "${optionalString (!hasPrefix ":" kw) ":"}${kw}") }`

: A function taking an attribute set with values

`keywordTransform`

: A function to format the names in an attrbute set. The default turns names into EDN keywords.

It returns a set with EDN-specific attributes `type`, `lib`, and
`generate` as specified [below](#pkgs-formats-result).

The `lib` attribute contains functions to be used in settings, for
generating special EDN values:

`mkRaw ednCode`

: Outputs the given string as raw EDN

`mkSet list`

: Outputs the given list as an EDN set, instead of the default
EDN vector

`mkList list`

: Outputs the given list as an EDN list, instead of the default
EDN vector

`mkKeyword string`

: Outputs the given string as an EDN keyword, instead of the default
EDN string

`mkSymbol string`

: Outputs the given string as an EDN symbol, instead of the default
EDN string

`mkTaggedElement tag value`

: Outputs an EDN tagged element


[]{#pkgs-formats-result}
These functions all return an attribute set with these values:
Expand Down
189 changes: 189 additions & 0 deletions pkgs/pkgs-lib/formats.nix
Expand Up @@ -459,4 +459,193 @@ rec {
'') {};
};

/* For configurations using [EDN].
Since EDN has more types than Nix, we need a way to map Nix types to
more than 1 EDN type. To that end, this format provides its own library,
and its own set of types.
A Nix string could correspond in EDN to a [String], a [Symbol] or a [Keyword].
A Nix array could correspond in EDN to a [List],[Set] or a [Vector].
Some more types exists, like fractions, regexes, but since they are less used,
we can leave the `mkRaw` function as an escape hatch.
[EDN]: <https://github.com/edn-format/edn>
[String]: <https://github.com/edn-format/edn#strings>
[Symbol]: <https://github.com/edn-format/edn#symbols>
[Keyword]: <https://github.com/edn-format/edn#keywords>
[List]: <https://github.com/edn-format/edn#lists>
[Set]: <https://github.com/edn-format/edn#sets>
[Vector]: <https://github.com/edn-format/edn#vectors>
*/
edn = with lib; { keywordTransform ? (kw: "${optionalString (!hasPrefix ":" kw) ":"}${kw}") }:
let
toEdn = 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 vector value else
abort "formats.edn: should never happen (value = ${value})";

escapeEdn = escape [ "\\" "#" "\"" ];
string = value: "\"${escapeEdn value}\"";

attrs = set:
if set ? _elixirType then specialType set
else
let
toKVPair = name: value: "${keywordTransform name} ${toEdn value}";
in
"{" + concatStringsSep ", " (mapAttrsToList toKVPair set) + "}";

listContent = values: concatStringsSep ", " (map toEdn values);

vector = values: "[" + (listContent values) + "]";

list = values: "(" + (listContent values) + ")";

set = values: "#{" + (listContent values) + "}";

keyword = value: ":${value}";

symbol = value: "${value}";

taggedElement = { tag, value }: "#${tag} ${toEdn value}";

specialType = { value, _ednType }:
if _ednType == "raw" then value else
if _ednType == "list" then list value else
if _ednType == "set" then set value else
if _ednType == "keyword" then keyword value else
if _ednType == "symbol" then symbol value else
if _ednType == "taggedElement" then taggedElement value else
abort "formats.elixirConf: should never happen (_ednType = ${_ednType})";

in
{
type = with lib.types; let
valueType = nullOr
(oneOf [
bool
int
float
str
path
(attrsOf valueType)
(listOf valueType)
]) // {
description = "EDN value";
};
in
valueType;

lib =
let
mkRaw = value: {
inherit value;
_ednType = "raw";
};

in
{
inherit mkRaw;

/* Make an Edn set out of a list.
*/
mkSet = value: {
inherit value;
_ednType = "set";
};

/* Make an Edn list out of a list.
*/
mkList = value: {
inherit value;
_ednType = "list";
};

/* Make an Edn keyword out of a string.
*/
mkKeyword = value: {
inherit value;
_ednType = "keyword";
};

/* Make an Edn symbol out of a string.
*/
mkSymbol = value: {
inherit value;
_ednType = "symbol";
};

/* Make an Edn tagged element.
*/
mkTaggedElement = tag: value: {
inherit tag value;
_ednType = "taggedElement";
};

/* Contains Edn types. Every type it exports can also be replaced
by raw Edn code (i.e. every type is `either type rawEdn`).
It also reexports standard types, wrapping them so that they can
also be raw Edn.
*/
types = with lib.types; let
isEdnType = type: x: (x._ednType or "") == type;

rawEdn = mkOptionType {
name = "rawEdn";
description = "raw edn";
check = isEdnType "raw";
};

ednOr = other: either other rawEdn;
in
{
inherit rawEdn ednOr;

list = ednOr (mkOptionType {
name = "ednList";
description = "edn list";
check = isEdnType "list";
});

set = ednOr (mkOptionType {
name = "ednSet";
description = "edn set";
check = isEdnType "set";
});

keyword = ednOr (mkOptionType {
name = "ednKeyword";
description = "edn keyword";
check = isEdnType "keyword";
});

symbol = ednOr (mkOptionType {
name = "ednSymbol";
description = "edn symbol";
check = isEdnType "symbol";
});

taggedElement = ednOr (mkOptionType {
name = "ednTaggedElement";
description = "edn taggedElement";
check = isEdnType "taggedElement";
});
# Wrap standard types, since anything in the Edn configuration
# can be raw Edn
} // lib.mapAttrs (_name: type: ednOr type) lib.types;
};

generate = name: value: pkgs.writeText name (toEdn value);

};

}

0 comments on commit c16892a

Please sign in to comment.