
Signed-off-by: faukah <fau@faukah.com> Change-Id: I6a6a6964cffaba7ce75488a48739398758d49363
323 lines
9.9 KiB
Nix
323 lines
9.9 KiB
Nix
# https://github.com/NixOS/nixpkgs/pull/426828
|
|
# The KDL document language (https://kdl.dev/)
|
|
/*
|
|
Original file: formats.nix
|
|
|
|
Copyright (c) 2003-2025 Eelco Dolstra and the Nixpkgs/NixOS contributors
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
a copy of this software and associated documentation files (the
|
|
"Software"), to deal in the Software without restriction, including
|
|
without limitation the rights to use, copy, modify, merge, publish,
|
|
distribute, sublicense, and/or sell copies of the Software, and to
|
|
permit persons to whom the Software is furnished to do so, subject to
|
|
the following conditions:
|
|
The above copyright notice and this permission notice shall be
|
|
included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
/*
|
|
* Modificatied by faukah (2025)
|
|
*
|
|
* This modified version is part of the Nichts project and is licensed under
|
|
* the GNU General Public License v3.0.
|
|
*
|
|
* See <https://www.gnu.org/licenses/> for details.
|
|
*/
|
|
{
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
|
|
inherit (lib.types)
|
|
listOf
|
|
attrsOf
|
|
str
|
|
bool
|
|
number
|
|
oneOf
|
|
nullOr
|
|
submodule
|
|
coercedTo
|
|
path
|
|
;
|
|
inherit (lib.lists) isList;
|
|
inherit (lib.attrsets) isAttrs;
|
|
inherit (lib.trivial) isBool;
|
|
in
|
|
{
|
|
type = (
|
|
let
|
|
mergeUniq =
|
|
mergeOne:
|
|
lib.mergeUniqueOption {
|
|
message = "";
|
|
merge =
|
|
loc: defs:
|
|
let
|
|
inherit (lib.head defs) file value;
|
|
in
|
|
mergeOne file loc value;
|
|
};
|
|
|
|
mergeFlat =
|
|
elemType: loc: file: value:
|
|
if value ? _type then
|
|
throw "${lib.showOption loc} has wrong type: expected '${elemType.description}', got `${value._type}`"
|
|
else
|
|
elemType.merge loc [ { inherit file value; } ];
|
|
|
|
uniqFlatListOf =
|
|
elemType:
|
|
lib.mkOptionType {
|
|
name = "uniqFlatListOf";
|
|
inherit (listOf elemType) description descriptionClass;
|
|
check = isList;
|
|
merge = mergeUniq (
|
|
file: loc: lib.imap1 (i: mergeFlat elemType (loc ++ [ "[entry ${toString i}]" ]) file)
|
|
);
|
|
};
|
|
|
|
uniqFlatAttrsOf =
|
|
elemType:
|
|
lib.mkOptionType {
|
|
name = "uniqFlatAttrsOf";
|
|
inherit (attrsOf elemType) description descriptionClass;
|
|
check = isAttrs;
|
|
merge = mergeUniq (file: loc: lib.mapAttrs (name: mergeFlat elemType (loc ++ [ name ]) file));
|
|
};
|
|
|
|
kdlUntypedValue = lib.mkOptionType {
|
|
name = "kdlUntypedValue";
|
|
description = "KDL value without type annotation";
|
|
descriptionClass = "noun";
|
|
|
|
inherit
|
|
(nullOr (oneOf [
|
|
str
|
|
bool
|
|
number
|
|
path
|
|
]))
|
|
check
|
|
merge
|
|
;
|
|
};
|
|
|
|
kdlTypedValue = lib.mkOptionType {
|
|
name = "kdlTypedValue";
|
|
description = "KDL value with type annotation";
|
|
descriptionClass = "noun";
|
|
|
|
check = isAttrs;
|
|
merge =
|
|
(submodule {
|
|
options = {
|
|
type = lib.mkOption {
|
|
type = nullOr str;
|
|
default = null;
|
|
description = ''
|
|
[Type annotation](https://kdl.dev/spec/#name-type-annotation) of a [KDL value](https://kdl.dev/spec/#name-value).
|
|
'';
|
|
};
|
|
value = lib.mkOption {
|
|
type = kdlUntypedValue;
|
|
description = ''
|
|
Scalar part of a [KDL value](https://kdl.dev/spec/#name-value)
|
|
'';
|
|
};
|
|
};
|
|
}).merge;
|
|
};
|
|
|
|
# https://kdl.dev/spec/#name-value
|
|
kdlValue = lib.mkOptionType {
|
|
name = "kdlValue";
|
|
description = "KDL value";
|
|
descriptionClass = "noun";
|
|
|
|
inherit (coercedTo kdlUntypedValue (value: { inherit value; }) kdlTypedValue) check merge;
|
|
|
|
nestedTypes = {
|
|
type = nullOr str;
|
|
scalar = kdlUntypedValue;
|
|
};
|
|
};
|
|
|
|
# https://kdl.dev/spec/#name-node
|
|
kdlNode = lib.mkOptionType {
|
|
name = "kdlNode";
|
|
description = "KDL node";
|
|
descriptionClass = "noun";
|
|
|
|
check = isAttrs;
|
|
merge =
|
|
(submodule {
|
|
options = {
|
|
type = lib.mkOption {
|
|
type = nullOr str;
|
|
default = null;
|
|
description = ''
|
|
[Type annotation](https://kdl.dev/spec/#name-type-annotation) of a KDL node.
|
|
'';
|
|
};
|
|
name = lib.mkOption {
|
|
type = str;
|
|
description = ''
|
|
Name of a [KDL node](https://kdl.dev/spec/#name-node).
|
|
'';
|
|
};
|
|
arguments = lib.mkOption {
|
|
type = uniqFlatListOf kdlValue;
|
|
default = [ ];
|
|
description = ''
|
|
[Arguments](https://kdl.dev/spec/#name-argument) of a KDL node.
|
|
'';
|
|
};
|
|
properties = lib.mkOption {
|
|
type = uniqFlatAttrsOf kdlValue;
|
|
default = { };
|
|
description = ''
|
|
[Properties](https://kdl.dev/spec/#name-property) of a KDL node.
|
|
'';
|
|
};
|
|
children = lib.mkOption {
|
|
type = kdlDocument;
|
|
default = [ ];
|
|
description = ''
|
|
[Children](https://kdl.dev/spec/#children-block) of a KDL node.
|
|
'';
|
|
};
|
|
};
|
|
}).merge;
|
|
|
|
nestedTypes = {
|
|
name = str;
|
|
type = nullOr str;
|
|
arguments = uniqFlatListOf kdlValue;
|
|
properties = uniqFlatAttrsOf kdlValue;
|
|
children = kdlDocument;
|
|
};
|
|
};
|
|
|
|
kdlDocument = lib.mkOptionType {
|
|
name = "kdlDocument";
|
|
description = "KDL document";
|
|
descriptionClass = "noun";
|
|
|
|
check = isList;
|
|
merge = mergeUniq (
|
|
file:
|
|
let
|
|
mergeDocument =
|
|
loc: toplevel:
|
|
builtins.concatLists (
|
|
lib.imap1 (i: mergeDocumentEntry (loc ++ [ "[entry ${toString i}]" ])) toplevel
|
|
);
|
|
|
|
mergeDocumentEntry =
|
|
loc: value:
|
|
let
|
|
inherit (lib.options) showDefs;
|
|
defs = [ { inherit file value; } ];
|
|
in
|
|
if isList value then
|
|
mergeDocument loc value
|
|
else if value ? _type then
|
|
if value._type == "if" then
|
|
if isBool value.condition then
|
|
if value.condition then mergeDocumentEntry loc value.content else [ ]
|
|
else
|
|
throw "`mkIf` called with non-Boolean condition at ${lib.showOption loc}. Definition value:${showDefs defs}"
|
|
else if value._type == "merge" then
|
|
throw ''
|
|
${lib.showOption loc} has wrong type: expected a KDL node or document, got 'merge'.
|
|
note: `mkMerge` is potentially ambiguous in a KDL document, as "merging" is application-specific. if you intended to "splat" all the nodes in a KDL document, you can just insert the list of nodes directly. you can arbitrarily nest KDL documents, and they will be concatenated.
|
|
''
|
|
else
|
|
throw "${lib.showOption loc} has wrong type: expected a KDL node or document, got '${value._type}'. Definition value:${showDefs defs}"
|
|
else if kdlNode.check value then
|
|
[ (kdlNode.merge loc [ { inherit file value; } ]) ]
|
|
else
|
|
throw "${lib.showOption loc} has wrong type: expected a KDL node or document. Definition value:${showDefs defs}";
|
|
in
|
|
mergeDocument
|
|
);
|
|
|
|
nestedTypes.node = kdlNode;
|
|
};
|
|
in
|
|
kdlDocument
|
|
);
|
|
|
|
lib = {
|
|
/**
|
|
Helper function for generating attrsets expected by pkgs.formats.kdl
|
|
# Example
|
|
```nix
|
|
let
|
|
settingsFormat = pkgs.formats.kdl { };
|
|
inherit (settingsFormat.lib) node;
|
|
in
|
|
settingsFormat.generate "sample.kdl" [
|
|
(node "foo" null [ ] { } [
|
|
(node "bar" null [ "baz" ] { a = 1; } [ ])
|
|
])
|
|
]
|
|
```
|
|
# Arguments
|
|
name
|
|
: The name of the node, represented by a string
|
|
type
|
|
: The type annotation of the node, represented by a string, or null to avoid generating a type annotation
|
|
arguments
|
|
: The arguments of the node, represented as a list of KDL values
|
|
properties
|
|
: The properties of the node, represented as an attrset of KDL values
|
|
children
|
|
: The children of the node, represented as a list of nodes
|
|
*/
|
|
node = name: type: arguments: properties: children: {
|
|
inherit
|
|
name
|
|
type
|
|
arguments
|
|
properties
|
|
children
|
|
;
|
|
};
|
|
|
|
/**
|
|
Helper function for generating the format of a typed value as expected by pkgs.formats.kdl
|
|
type
|
|
: The type of the value, represented by a string
|
|
value
|
|
: The value itself
|
|
*/
|
|
typed = type: value: { inherit type value; };
|
|
};
|
|
|
|
generate =
|
|
name: value:
|
|
pkgs.callPackage (
|
|
{ runCommand, jsonkdl }:
|
|
runCommand name
|
|
{
|
|
nativeBuildInputs = [ jsonkdl ];
|
|
value = builtins.toJSON value;
|
|
passAsFile = [ "value" ];
|
|
}
|
|
''
|
|
jsonkdl --kdl-v1 -- "$valuePath" "$out"
|
|
''
|
|
) { };
|
|
}
|