{
  nftables
, writeScript
, lib
} :
name : ruleset :
let
  inherit (lib.strings) concatStringsSep splitString hasInfix substring;
  inherit (lib.lists) groupBy;
  inherit (lib.attrsets) mapAttrsToList nameValuePair;
  inherit (builtins) map listToAttrs replaceStrings head tail;

  indentLines = offset : lines :
    if lines == []
    then ""
    else
      let
        line = head lines;
        isOpen = hasInfix "{" line;
        isClose = hasInfix "}" line;
        offset' = offset +
                 (if isOpen then 4 else 0) +
                 (if isClose then -4 else 0);
        padding = offset: substring 0 offset "                           ";
      in
        if (isClose && !isOpen)
        then
          (padding offset') + line + "\n" + indentLines offset' (tail lines)
        else
          (padding offset) + line + "\n" + indentLines offset' (tail lines);

  indent = text : indentLines 0 (splitString "\n" text);

  dochain = { name, type, family, rules,
              policy ? null,
              priority ? "filter",
              hook ? null } : ''
    chain ${name} {
    ${if hook != null
      then "type ${type} hook ${hook} priority ${priority}; policy ${policy};"
    else ""
    }
    ${concatStringsSep "\n" rules}
    }
  '';
  dotable = family : chains : ''
    table ${family} table-${family} {
    ${concatStringsSep "\n" (map dochain chains)}
    }
  '';
  categorise = chains :
    groupBy
      ({ family, ... } : family)
      (mapAttrsToList (n : v : v // { name = n; }) chains);
in writeScript name ''
#!${nftables}/sbin/nft -f

flush ruleset

${indent (concatStringsSep "\n" (mapAttrsToList dotable (categorise ruleset)))}
''