NixOS / nixpkgs

Nix Packages collection & NixOS
MIT License
17.44k stars 13.64k forks source link

request: `pkgs.formats.kdl` to support applications adopting KDL config language #198655

Open colemickens opened 1 year ago

colemickens commented 1 year ago

Project description

Zellij, for example, as adopted KDL as a config language/format.

It would be nice if there was similar support for KDL like we have for JSON/YAML/etc.

AgathaSorceress commented 1 year ago

I looked into this and this seems to unfortunately be more complicated than something like JSON/YAML, due to KDL nodes not only being key:value pairs, but also having arguments and properties.

Here's an example KDL document consisting of one node

thermal-paste "NT-H1" amount="3.5g" {
    description "it tastes nice :)"
}

It could be represented in nix like this:

{
  thermal-paste = {
    properties = { amount = "3.5g"; };
    arguments = [ "NT-H1" ];
    children = { description = { arguments = [ "it tastes nice :)" ]; }; };
  };
}

by passing this attribute set to builtins.toJSON, we get:

{
  "thermal-paste": {
    "arguments": [
      "NT-H1"
    ],
    "children": {
      "description": {
        "arguments": [
          "it tastes nice:)"
        ]
      }
    },
    "properties": {
      "amount": "3.5g"
    }
  }
}

Which is quite ugly, but could then be recursively parsed by an external program (either a custom implementation of json2kdl similar to json2yaml, or (this option might be easier) upstreaming it into remarshal).

The parser would need to make these assumptions:

Parsing the actual values should not be a problem, as there are already quite a few KDL libraries for most common languages. KDL's 4 data types should not be a problem either, as both Nix and JSON have them, but Null might be tricky due to some parsers making the assumption that "explicit absence of a value" and "a value not being specified" are the same.

Potential problems

AgathaSorceress commented 1 year ago

I have written a program that converts JSON to KDL here In theory, the only things left are:

feathecutie commented 6 months ago

It could be represented in nix like this:

{
  thermal-paste = {
    properties = { amount = "3.5g"; };
    arguments = [ "NT-H1" ];
    children = { description = { arguments = [ "it tastes nice :)" ]; }; };
  };
}

Note that this representation makes the assumption that each node identifier can only occur once per parent, which is not necessarily the case in KDL. This representation also does not necessarily preserve the order of nodes (unless the order of attributes is somehow preserved consistently in Nix?), which is significant in KDL.

As far as I can tell, the proposed function representation (referenced below for clarity) would solve this issue, but I'm not sure how to properly implement this in pkgs.formats.

node = name: arguments: properties: children: { ${name} = { inherit arguments properties children; }; }

I've played around with integrating the proposed attrset representation into pkgs.formats using @AgathaSorceress's json2kdl program until I noticed this issue, and could provide this experimentation for reference.

To expand on representing KDL documents as attrsets, which seems to be the preferred/required representation for pkgs.formats, it is probably necessary to represent the document and all lists of children as actual Nix lists instead of attrsets, to allow duplicate identifiers and preserving order. The representation of nodes inside this list could be something like this:

{
  identifier = "thermal-paste";
  properties = { amount = "3.5g"; };
  arguments = [ "NT-H1" ];
  children = [
    {
      identifier = "description";
      arguments = [ "it tastes nice :)" ];
    }
  ];
}

Using this representation, it would also be very easy to provide an additional node helper function that can convert the more idiomatic function call syntax into this structure:

node = identifier: arguments: properties: children: { inherit identifier arguments properties children; }

or even:

node = {identifier, arguments ? [], properties ? {}, children ? []}: { inherit identifier arguments properties children; }

I will try to further experiment with this, and might try to upstream it if I manage to create a working implementation.

feathecutie commented 6 months ago

For reference, there is now a working(?) implementation of this at https://github.com/feathecutie/nixpkgs/tree/formats-kdl.

The only remaining issue is that I can't get the NixOS manual to build on this branch when I define any NixOS option with type (pkgs.formats.kdl {}).type. Instead, I keep encountering error: stack overflow (possible infinite recursion) when trying to build the manual using something like nix-build nixos/release.nix -A manual.x86_64-linux, probably because it is trying to recursively generate the type documentation even though the type documentation should be overridden.

Feel free to use this code (and feel free to open a nixpkgs PR with it if you manage to fix this, otherwise I'll probably keep trying stuff out and open a PR myself).